Home > Article > Web Front-end > How to use the effect of Vue3 responsive core
Normally we will not use effect directly, because effect is a low-level API. When we use Vue3, Vue will call effect for us by default. Effect is translated as effect, which means to make it work. The function that makes it work is the function we pass in, so the function of effect is to make the function we pass in take effect, that is, to execute this function. A simplified diagram of the execution process is as follows:
Next, we will first understand the basic usage of effect through examples, and then understand the principle.
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
The result will print 1 first, and then print 2 after obj.count
.
The process diagram is as follows:
Run effect(fun)
// 先执行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //执行fun }, stop() { } }
console.log (obj.count)
The track dependency collection structure is as follows:
obj.count
Trigger dependencies, execute runner.run(), and actually run is
() => { console.log(obj.count) }
, so it prints out 2
When this value is true, only after the first manual call to the runner, the dependent data The callback of the effect will be automatically executed when the change is made. It can be understood that the effect is executed for the first time after manually calling the runner.
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
will only print out 2
. The reason is that the effect source code has the following Logic:
let events = [] const onTrack = (e) => { events.push(e) } const obj = reactive({ foo: 1, bar: 2 }) const runner = effect( () => { console.log(obj.foo) }, { onTrack } ) console.log('runner', runner) obj.foo++ console.log("events", events)
Look at the printed results of events:
[ { effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是哪个响应式数据发生了变化 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值 key: 'foo' } ]
// packages/reactivity/src/effect.ts export interface ReactiveEffectOptions extends DebuggerOptions { lazy?: boolean scheduler?: EffectScheduler scope?: EffectScope allowRecurse?: boolean onStop?: () => void } export function effect<T = any>( fn: () => T, // 副作用函数 options?: ReactiveEffectOptions // 结构如上 ): ReactiveEffectRunner { // 如果 fn 对象上有 effect 属性 if ((fn as ReactiveEffectRunner).effect) { // 那么就将 fn 替换为 fn.effect.fn fn = (fn as ReactiveEffectRunner).effect.fn } // 创建一个响应式副作用函数 const _effect = new ReactiveEffect(fn) if (options) { // 将配置项合并到响应式副作用函数上 extend(_effect, options) // 如果配置项中有 scope 属性(该属性的作用是指定副作用函数的作用域) if (options.scope) recordEffectScope(_effect, options.scope) } if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() } // _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner // 将响应式副作用函数赋值给 runner.effect runner.effect = _effect return runner }
Core code:
Create a responsive side effect functionconst _effect = new ReactiveEffect( fn)
, the running results are as follows:
Execute responsive side effect function in non-lazy state_effect.run()
if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() }
_effect.run
Scope is bound to _effect
// _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
Returns the side effect function runner
export class ReactiveEffect<T = any> { active = true deps: Dep[] = [] // 响应式依赖项的集合 parent: ReactiveEffect | undefined = undefined /** * Can be attached after creation * @internal */ computed?: ComputedRefImpl<T> /** * @internal */ allowRecurse?: boolean /** * @internal */ private deferStop?: boolean onStop?: () => void // dev only onTrack?: (event: DebuggerEvent) => void // dev only onTrigger?: (event: DebuggerEvent) => void constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { // 记录当前 ReactiveEffect 对象的作用域 recordEffectScope(this, scope) } run() { // 如果当前 ReactiveEffect 对象不处于活动状态,直接返回 fn 的执行结果 if (!this.active) { return this.fn() } // 寻找当前 ReactiveEffect 对象的最顶层的父级作用域 let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack // 是否要跟踪 while (parent) { if (parent === this) { return } parent = parent.parent } try { // 记录父级作用域为当前活动的 ReactiveEffect 对象 this.parent = activeEffect activeEffect = this // 将当前活动的 ReactiveEffect 对象设置为 “自己” shouldTrack = true // 将 shouldTrack 设置为 true (表示是否需要收集依赖) // effectTrackDepth 用于标识当前的 effect 调用栈的深度,执行一次 effect 就会将 effectTrackDepth 加 1 trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { // 初始依赖追踪标记 initDepMarkers(this) } else { // 清除依赖追踪标记 cleanupEffect(this) } // 返回副作用函数执行结果 return this.fn() } finally { // 如果 effect调用栈的深度 没有超过阈值 if (effectTrackDepth <= maxMarkerBits) { // 确定最终的依赖追踪标记 finalizeDepMarkers(this) } // 执行完毕会将 effectTrackDepth 减 1 trackOpBit = 1 << --effectTrackDepth // 执行完毕,将当前活动的 ReactiveEffect 对象设置为 “父级作用域” activeEffect = this.parent // 将 shouldTrack 设置为上一个值 shouldTrack = lastShouldTrack // 将父级作用域设置为 undefined this.parent = undefined // 延时停止,这个标志是在 stop 方法中设置的 if (this.deferStop) { this.stop() } } } stop() { // stopped while running itself - defer the cleanup // 如果当前 活动的 ReactiveEffect 对象是 “自己” // 延迟停止,需要执行完当前的副作用函数之后再停止 if (activeEffect === this) { // 在 run 方法中会判断 deferStop 的值,如果为 true,就会执行 stop 方法 this.deferStop = true } else if (this.active) {// 如果当前 ReactiveEffect 对象处于活动状态 cleanupEffect(this) // 清除所有的依赖追踪标记 if (this.onStop) { this.onStop() } this.active = false // 将 active 设置为 false } } }
The function of the run method is to execute the side effect function, and in the process of executing the side effect function, dependencies will be collected;
The function of the stop method is to stop the current ReactiveEffect object, after stopping, will no longer collect dependencies;
activeEffect and this are not equal every time, because activeEffect will change with the depth of the call stack, and this It is fixed;
In the side effect function, obj. count
will trigger dependency collection
const runner = effect(() => { console.log(obj.count) })
The triggered entry is in the get interceptor
function createGetter(isReadonly = false, shallow = false) { // 闭包返回 get 拦截器方法 return function get(target: Target, key: string | symbol, receiver: object) { // ... if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // ... }
const targetMap = new WeakMap(); /** * 收集依赖 * @param target target 触发依赖的对象,例子中的obj * @param type 操作类型 比如obj.count就是get * @param key 指向对象的key, 比如obj.count就是count */ export function track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { // 是否应该依赖收集 & 当前的new ReactiveEffect()即指向的就是当前正在执行的副作用函数 // 如果 targetMap 中没有 target,就会创建一个 Map 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())) // createDep 生成dep = { w:0, n: 0} } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
shouldTrack has also been mentioned above. The function is to control whether to collect dependencies;
activeEffect is the ReactiveEffect object we just talked about, which points to the side effect function currently being executed;
The function of the track method is to collect dependencies, and its The implementation is very simple, just record the target and key in targetMap;
targetMap is a WeakMap, its key is target, its value is a Map, the key of this Map is key, and its value is a Set;
The structural pseudocode of targetMap is as follows:
targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }
The above is the original depMap
The dev environment will increase ## to increase responsive debugging #eventInfo
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefinedThe eventInfo structure is as follows:
trackEffects(dep, eventInfo)
trackEffects
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { // 执行之前 dep = Set(0) {w: 0, n: 0} // 执行之后 dep = Set(0) {w: 0, n: 2} dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 将activeEffect添加到dep dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { // onTrack逻辑 activeEffect!.onTrack( extend( { effect: activeEffect! }, debuggerEventExtraInfo! ) ) } } }
dep.add(activeEffect!) If there is no current ReactiveEffect object in dep, it will be added
obj.count in the example will trigger set interception , trigger dependency update
function createSetter(shallow = false) { return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { //... 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) // 触发ADD依赖更新 } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key, value, oldValue) //触发SET依赖更新 } } //... }
// 路径:packages/reactivity/src/effect.ts export function trigger( target: object, type: TriggerOpTypes, key?: unknown, newValue?: unknown, oldValue?: unknown, oldTarget?: Map<unknown, unknown> | Set<unknown> ) { const depsMap = targetMap.get(target) // 获取depsMap, targetMap是在track中创建的依赖 if (!depsMap) { // never been tracked return } let deps: (Dep | undefined)[] = [] if (type === TriggerOpTypes.CLEAR) { // collection being cleared // trigger all effects for target deps = [...depsMap.values()] } else if (key === 'length' && isArray(target)) { const newLength = Number(newValue) depsMap.forEach((dep, key) => { if (key === 'length' || key >= newLength) { deps.push(dep) } }) } else { // schedule runs for SET | ADD | DELETE if (key !== void 0) { deps.push(depsMap.get(key)) } // also run for iteration key on ADD | DELETE | Map.SET switch (type) { case TriggerOpTypes.ADD: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } else if (isIntegerKey(key)) { // new index added to array -> length changes deps.push(depsMap.get('length')) } break case TriggerOpTypes.DELETE: if (!isArray(target)) { deps.push(depsMap.get(ITERATE_KEY)) if (isMap(target)) { deps.push(depsMap.get(MAP_KEY_ITERATE_KEY)) } } break case TriggerOpTypes.SET: if (isMap(target)) { deps.push(depsMap.get(ITERATE_KEY)) } break } } const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } } else { const effects: ReactiveEffect[] = [] for (const dep of deps) { if (dep) { effects.push(...dep) } } if (__DEV__) { triggerEffects(createDep(effects), eventInfo) } else { triggerEffects(createDep(effects)) } } }
const depsMap = targetMap.get(target)
获取 targetMap 中的 depsMap targetMap结构如下:
执行以上语句之后的depsMap结构如下:
将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))
之后的deps结构如下:
triggerEffects(deps[0], eventInfo)
const eventInfo = __DEV__ ? { target, type, key, newValue, oldValue, oldTarget } : undefined if (deps.length === 1) { if (deps[0]) { if (__DEV__) { triggerEffects(deps[0], eventInfo) } else { triggerEffects(deps[0]) } } }
trigger函数的作用就是触发依赖,当我们修改数据的时候,就会触发依赖,然后执行依赖中的副作用函数。
在这里的实现其实并没有执行,主要是收集一些需要执行的副作用函数,然后在丢给triggerEffects函数去执行,接下来看看triggerEffects函数。
export function triggerEffects( dep: Dep | ReactiveEffect[], debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // spread into array for stabilization const effects = isArray(dep) ? dep : [...dep] for (const effect of effects) { if (effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } for (const effect of effects) { if (!effect.computed) { triggerEffect(effect, debuggerEventExtraInfo) } } }
主要步骤
const effects = isArray(dep) ? dep : [...dep]
获取effects
triggerEffect(effect, debuggerEventExtraInfo)
执行effect,接下来看看源码
function triggerEffect( effect: ReactiveEffect, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { if (effect !== activeEffect || effect.allowRecurse) { // 如果 effect.onTrigger 存在,就会执行,只有开发模式下才会执行 if (__DEV__ && effect.onTrigger) { effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) } // 如果 effect 是一个调度器,就会执行 scheduler if (effect.scheduler) { effect.scheduler() } else { // 其它情况执行 effect.run() effect.run() } } }
effect.run()就是执行副作用函数
The above is the detailed content of How to use the effect of Vue3 responsive core. For more information, please follow other related articles on the PHP Chinese website!