ホームページ > 記事 > ウェブフロントエンド > Vue3レスポンシブコアのエフェクトの使い方
エフェクトは低レベル API であるため、通常はエフェクトを直接使用しません。Vue3 を使用すると、デフォルトで Vue がエフェクトを呼び出します。 Effect は効果と訳され、機能させるという意味ですが、機能させる機能とは渡した関数のことなので、エフェクトの機能は渡した機能を有効にする、つまりこの機能を実行することになります。実行プロセスの簡略図は次のとおりです。
次に、最初に例を通してエフェクトの基本的な使用法を理解し、次に原理を理解します。
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }) obj.count++
結果は最初に 1 を出力し、obj.count
の後に 2 を出力します。
プロセス図は次のとおりです:
実行 effect(fun)
// 先执行 fun() // 打印出1 const runner = new ReactiveEffect(fn) return runner runner: { run() { this.fun() //执行fun }, stop() { } }
console .log (obj.count)
追跡依存関係の収集構造は次のとおりです:
obj.count
依存関係をトリガーし、runner.run を実行します。 ()、実際の実行は
() => { console.log(obj.count) }
であるため、2
この値が true の場合、最初のマニュアルの後のみ依存データであるランナーへの呼び出し 変更が行われるとエフェクトのコールバックが自動的に実行されます 手動でランナーを呼び出して初めてエフェクトが実行されることが分かります
const obj = reactive({count: 1}) const runner = effect(() => { console.log(obj.count) }, { lazy: true }) runner() obj.count++
will 2
のみを出力します。その理由は、エフェクトのソース コードに次のロジックがあるためです:
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)## が含まれます#イベントの出力結果を見る:
[ { effect: runner, // effect 函数的返回值 target: toRaw(obj), // 表示的是哪个响应式数据发生了变化 type: TrackOpTypes.GET, // 表示此次记录操作的类型。 get 表示获取值 key: 'foo' } ]2. ソースコード解析1. エフェクトメソッドの実装
// 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 }
const _effect = new ReactiveEffect( fn) を作成します。実行結果は次のとおりです。
# #応答性の高い副作用関数を非遅延状態で実行します
_effect.run()<pre class="brush:js;">if (!options || !options.lazy) { // options.lazy 不为true
_effect.run() // 执行响应式副作用函数 首次执行fn()
}</pre>
スコープは _effect
# にバインドされています##
// _effect.run作用域绑定到_effect const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
副作用関数ランナーを返します
2、ReactiveEffect 関数のソース コード##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 } } }
stop メソッドの機能は、現在の ReactiveEffect オブジェクトを停止することであり、停止後は収集されなくなります。依存関係;
activeEffect と this は毎回等しくありません。activeEffect は呼び出しスタックの深さによって変化し、これは固定されているためです。
3. 依存関係の収集関連
const runner = effect(() => { console.log(obj.count) })
トリガーされたエントリは get インターセプターにあります <pre class="brush:js;">function createGetter(isReadonly = false, shallow = false) {
// 闭包返回 get 拦截器方法
return function get(target: Target, key: string | symbol, receiver: object) {
// ...
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// ...
}</pre>
2. トラック ソース コード
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 についても上で説明しました。関数は依存関係を収集するかどうかを制御することです。
track メソッドの機能は依存関係を収集することであり、その実装は非常に単純です。 targetMap にターゲットとキーを記録します;
targetMap は WeakMap、そのキーはターゲット、その値は Map、この Map のキーはキー、その値は Set;
targetMap の構造的な疑似コードは次のとおりです:
targetMap = { target: { key: dep }, // 比如: obj: { count: { w: 0, n: 0 } } }上記は元の depMap
#応答性の高いデバッグを強化するために ## 開発環境が増加します # eventInfo#
const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefinedeventInfo の構造は次のとおりです:
trackEffects
3 を参照してください。 trackEffects(dep,eventInfo) ソースコードの解釈<pre class="brush:js;">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!
)
)
}
}
}</pre>
dep に現在の ReactiveEffect オブジェクトがない場合は追加されます
4. トリガーの依存関係
たとえば、コードこの例の obj.count
は set interception をトリガーし、依存関係の 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()就是执行副作用函数
以上がVue3レスポンシブコアのエフェクトの使い方の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。