ホームページ  >  記事  >  ウェブフロントエンド  >  Vue3レスポンシブコアのエフェクトの使い方

Vue3レスポンシブコアのエフェクトの使い方

WBOY
WBOY転載
2023-05-10 11:19:192726ブラウズ

エフェクトは低レベル API であるため、通常はエフェクトを直接使用しません。Vue3 を使用すると、デフォルトで Vue がエフェクトを呼び出します。 Effect は効果と訳され、機能させるという意味ですが、機能させる機能とは渡した関数のことなので、エフェクトの機能は渡した機能を有効にする、つまりこの機能を実行することになります。実行プロセスの簡略図は次のとおりです。

Vue3レスポンシブコアのエフェクトの使い方

次に、最初に例を通してエフェクトの基本的な使用法を理解し、次に原理を理解します。

1. エフェクトの使用法

1. 基本的な使用法

const obj = reactive({count: 1})

const runner = effect(() => {
  console.log(obj.count)
})

obj.count++

結果は最初に 1 を出力し、obj.count の後に 2 を出力します。

プロセス図は次のとおりです:

Vue3レスポンシブコアのエフェクトの使い方

実行 effect(fun)

// 先执行
fun()  // 打印出1

const runner = new ReactiveEffect(fn)

return runner

runner: {
  run() {
    this.fun() //执行fun
  },
  stop() {

  }
}

console .log (obj.count)追跡依存関係の収集構造は次のとおりです:

Vue3レスポンシブコアのエフェクトの使い方

obj.count 依存関係をトリガーし、runner.run を実行します。 ()、実際の実行は

() => {
  console.log(obj.count)
}

であるため、2

2 が出力されます。lazy 属性は true

この値が true の場合、最初のマニュアルの後のみ依存データであるランナーへの呼び出し 変更が行われるとエフェクトのコールバックが自動的に実行されます 手動でランナーを呼び出して初めてエフェクトが実行されることが分かります

const obj = reactive({count: 1})

const runner = effect(() => {
  console.log(obj.count)
}, {
  lazy: true
})
runner()
obj.count++

will 2

のみを出力します。その理由は、エフェクトのソース コードに次のロジックがあるためです:

Vue3レスポンシブコアのエフェクトの使い方

3. オプションには onTrack

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)
## が含まれます#イベントの出力結果を見る:

Vue3レスポンシブコアのエフェクトの使い方

[
  {
    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
}

Coreコード:

応答性の高い副作用関数

const _effect = new ReactiveEffect( fn) を作成します。実行結果は次のとおりです。

Vue3レスポンシブコアのエフェクトの使い方# #応答性の高い副作用関数を非遅延状態で実行します

_effect.run()

<pre class="brush:js;">if (!options || !options.lazy) { // options.lazy 不为true _effect.run() // 执行响应式副作用函数 首次执行fn() }</pre>

_effect.run

スコープは _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
    }
  }
}

run メソッドの機能は副作用関数を実行することです。 、および副作用関数を実行するプロセスで、依存関係が収集されます;

  • stop メソッドの機能は、現在の ReactiveEffect オブジェクトを停止することであり、停止後は収集されなくなります。依存関係;

  • activeEffect と this は毎回等しくありません。activeEffect は呼び出しスタックの深さによって変化し、これは固定されているためです。

  • 3. 依存関係の収集関連

  • 1. 依存関係の収集をトリガーする方法

副作用関数では、

obj.count

が依存関係の収集をトリガーします

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 についても上で説明しました。関数は依存関係を収集するかどうかを制御することです。

activeEffect は、先ほど説明した ReactiveEffect オブジェクトで、現在実行中の副作用関数を指します。

track メソッドの機能は依存関係を収集することであり、その実装は非常に単純です。 targetMap にターゲットとキーを記録します;

targetMap は WeakMap、そのキーはターゲット、その値は Map、この Map のキーはキー、その値は Set;

targetMap の構造的な疑似コードは次のとおりです:

targetMap = {
  target: { 
    key: dep
  },
  // 比如:
  obj: { 
    count: {
       w: 0, 
       n: 0
    }
  }
}

上記は元の depMap

#応答性の高いデバッグを強化するために ## 開発環境が増加します # eventInfoVue3レスポンシブコアのエフェクトの使い方#

const eventInfo = __DEV__
  ? { effect: activeEffect, target, type, key }
  : undefined

eventInfo の構造は次のとおりです:

trackEffects(dep,eventInfo)

#if 現在の ReactiveEffect オブジェクトが dep にない場合、それが追加されます。その効果は、オブジェクトのプロパティ操作を副作用関数に関連付けることです。次に、

trackEffectsVue3レスポンシブコアのエフェクトの使い方

3 を参照してください。 trackEffects(dep,eventInfo) ソースコードの解釈<pre class="brush:js;">export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth &lt;= 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__ &amp;&amp; activeEffect!.onTrack) { // onTrack逻辑 activeEffect!.onTrack( extend( { effect: activeEffect! }, debuggerEventExtraInfo! ) ) } } }</pre>

dep.add(activeEffect!)

dep に現在の ReactiveEffect オブジェクトがない場合は追加されます

最終的に生成される depTarget 構造は次のとおりです:

4. トリガーの依存関係 Vue3レスポンシブコアのエフェクトの使い方

たとえば、コード

この例の 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&#39;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依赖更新
      }
    }
    //...
  }

1、trigger依赖更新

// 路径: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 === &#39;length&#39; && isArray(target)) {
    const newLength = Number(newValue)
    depsMap.forEach((dep, key) => {
      if (key === &#39;length&#39; || 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(&#39;length&#39;))
        }
        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结构如下:

Vue3レスポンシブコアのエフェクトの使い方

执行以上语句之后的depsMap结构如下:

Vue3レスポンシブコアのエフェクトの使い方

将 depsMap 中 key 对应的 ReactiveEffect 对象添加到 deps 中deps.push(depsMap.get(key))之后的deps结构如下:

Vue3レスポンシブコアのエフェクトの使い方

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函数。

2、triggerEffects(deps[0], eventInfo)

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

Vue3レスポンシブコアのエフェクトの使い方

triggerEffect(effect, debuggerEventExtraInfo)执行effect,接下来看看源码

3、triggerEffect(effect, debuggerEventExtraInfo)

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 サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。