Vue 3.2.26 source code interpretation (a) Reactivity responsive vUE 3.2.26 source code interpretation (two) initialize render Vue (three) Diff algorithm principle
Overall flow chart
demo
Look at this code with two questions in mind
- What is the return value of reactive?
- How does effect sense changes in internal responsive data?
test('base reactive'.() = >{
const a = {
bbb: 1
};
const ar = reactive(a)
let dummy
effect(() = > (
dummy = ar.bbb
))
expect(dummy).toBe(1)
ar.bbb = 2;
expect(dummy).toBe(2)})Copy the code
With these two questions in mind, let’s look at the core logic in the code
Reactive method
- Check whether it is read-only
- call
createReactiveObject
Returns a reactive object
export function reactive<T extends object> (target: T) :UnwrapNestedRefs<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy.return the readonly version.
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}
Copy the code
createReactiveObject
- Verify that the agent object is valid
- Use global proxyMap to save proxy objects for next invocation
- Using a Proxy
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>
) {
/ /... Check logic logic
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
Copy the code
The above delegate logic is written in baseHandlers to delegate ordinary objects, so let’s look at the code step by step.
Effect method
- create
ReactiveEffect
- Judgment is
options
Configure and whetherlazy
Determines whether to call it the first timeReactiveEffect
In therun
methods - The specified
run
In the methodthis
Point to the
export function effect<T = any> (fn: () => T, options? : ReactiveEffectOptions) :ReactiveEffectRunner {
/ /... Check logic elision
const _effect = new ReactiveEffect(fn)
/ /... Non-mainline logic elision
if(! options || ! options.lazy) { _effect.run() }const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
Copy the code
ReactiveEffect
- The constructor
constructor
Three parameters are passed to thefn
scheduler
.scope
, we focus on the first parameter
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
// can be attached after creationcomputed? :booleanallowRecurse? :booleanonStop? :() = > void
// dev onlyonTrack? :(event: DebuggerEvent) = > void
// dev onlyonTrigger? :(event: DebuggerEvent) = > void
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null, scope? : EffectScope |null
) {
recordEffectScope(this, scope)
}
run() {
try {
effectStack.push((activeEffect = this)
/ /...
return this.fn()
} finally {
/ /...
effectStack.pop()
const n = effectStack.length
activeEffect = n > 0 ? effectStack[n - 1] : undefined}}stop() {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false}}}Copy the code
Core logic in the run method:
- The current
effect
Assigned toactiveEffect
And pusheffectStack
- perform
fn
, the function passed in by the user. - reset
activeEffect
.effectStack
state
When we execute fn, we trigger the GET operation on the AR object, so let’s see what we’re doing here
effect(() = > (
dummy = ar.bbb
))
Copy the code
baseHandlers get
- Gets the current
key
The corresponding original object value - Track the current
key
The dependence of - Determines whether the original value is an object and needs to be returned
reactive
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
/ /...
const res = Reflect.get(target, key, receiver)
/ /...
if(! isReadonly) { track(target, TrackOpTypes.GET, key) }/ /...
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
Copy the code
Track dependence
- Create a dependency comparison table
- each
obj
Corresponds to a dependencyWeekMap
- Object for each
key
Corresponds to aSet
Set
Store current inkey
The correspondingeffect
const targetMap = new WeakMap<any, KeyToDepMap>()
export function track(target: object.type: TrackOpTypes, key: unknown) {
let depsMap = targetMap.get(target)
if(! depsMap) { targetMap.set(target, (depsMap =new Map()))}let dep = depsMap.get(key)
if(! dep) { depsMap.set(key, (dep = createDep())) } trackEffects(dep) }export constcreateDep = (effects? : ReactiveEffect[]):Dep= > {
const dep = new Set<ReactiveEffect>(effects) as Dep
dep.w = 0
dep.n = 0
return dep
}
export function trackEffects(dep: Dep, debuggerEventExtraInfo? : DebuggerEventExtraInfo) { dep.add(activeEffect!) activeEffect! .deps.push(dep) }Copy the code
Now that the dependency collection process is over, let’s look at the logic when we update
baseHandlers set
- Compare the old and new values to determine whether new attributes are added
- If yes, check whether the old and new values are the same
trigger
logic
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) :boolean {
/ /...
let oldValue = (target as any)[key]
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) }else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
Copy the code
Trigger update
- Get current dependencies
Map
- According to the
key
fromMap
To obtain the correspondingeffects
- Traverse the execution
effects
To determineeffects
On whether there isscheduler
If so, executescheduler
Otherwise dorun
Methods.
export function trigger(
target: object.type: TriggerOpTypes, key? : unknown, newValue? : unknown, oldValue? : unknown, oldTarget? :Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
/ /...
let deps: (Dep | undefined=) [] []/ /...
deps.push(depsMap.get(key))
/ /...
triggerEffects(deps[0])}export function triggerEffects(dep: Dep | ReactiveEffect[], debuggerEventExtraInfo? : DebuggerEventExtraInfo) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {
if(effect ! == activeEffect || effect.allowRecurse) {if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}
}
Copy the code
Finally, let’s review the picture to see if it’s much clearer: