vue 3 reactivity 原理分析

105 阅读2分钟

vue3 的源码结构如下

├── compiler-core // 编译
├── compiler-dom // 
├── compiler-sfc // .vue文件编译
├── compiler-ssr // 服务端编译
├── global.d.ts
├── reactivity // 响应式核心
├── runtime-core //  运行时
├── runtime-dom // web运行时
├── runtime-test
├── server-renderer // 服务端渲染
├── shared
├── size-check
├── template-explorer
└── vue

@vue/reactivity 是vue3响应式的核心,也是本文要分析的

先看一段摘自官方源码的测试用例:

it('should observe basic properties', () => {
    let dummy
    const counter = reactive({ num: 0 })
    effect(() => (dummy = counter.num))
    expect(dummy).toBe(0)
    counter.num = 7
    expect(dummy).toBe(7)
  })

问题:dummy的值什么时候被改变的?

关键概念:

  • reactivity: 响应式对象
  • effect: reactivity改变后所产生的效果
  • computed和ref是基于上述两者的封装

简化流程:

  • 1.定义reactiviy对象
  • 2.收集依赖
  • 3.改变reactivity属性
  • 4.触发effect,产品效果

上述测试用例具体过程分析:

  • 1.定义reactivity对象: 通过es6的proxy定义对象的set,get方法:reactivity({ num: 0 }),返回一个reactivity对象
  • 2.定义effect:定义effect(() => (dummy = counter.num))
  • 3.收集依赖: 定义effect后会立即执行 () => (dummy = counter.num),触发reactivity对象的get方法收集依赖,这里收集的是要触发的effect
  • 4.改变reactivity的属性:执行counter.num = 7
  • 5.触发effect:触发reactivity对象的set方法,执行effect: () => (dummy = counter.num)

经过上面的分析,应该就知道dummy的值什么的时候被改变的了

代码分析:

// 定义reactivity对象的时候会设置reactivity对象的get,set代理,如下截取了部分关键代码
// reactive.ts
// reactivity对象属性的get代理,收集依赖关键在track方法
function get(target: Target, key: string | symbol, receiver: object) {
    // ...省略
    if (!isReadonly) {
      // 收集依赖
      track(target, TrackOpTypes.GET, key)
    }
    // ...省略
}

// reactivity对象属性的set代理,触发effect的关键的在 trigger方法
function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {

    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
      	// 触发effect 
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
}
// effect.ts
// track方法
function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
 // ...省略部分
}
// effect.ts
// trggier 方法
function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
  const effects = new Set<ReactiveEffect>()
  // ...省略
  const run = (effect: ReactiveEffect) => {
    if (__DEV__ && effect.options.onTrigger) {
      effect.options.onTrigger({
        effect,
        target,
        key,
        type,
        newValue,
        oldValue,
        oldTarget
      })
    }
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
  effects.forEach(run)
 }
 // effect.ts
// effect 长什么样?
function effect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
  if (isEffect(fn)) {
    fn = fn.raw
  }
  const effect = createReactiveEffect(fn, options)
  //  创建effect后,立即执行effect,就完成收集依赖
  if (!options.lazy) {
    effect()
  }
  return effect
}
// effect.ts
// 通过createReactiveEffect创建effect
function createReactiveEffect<T = any>(
  fn: () => T,
  options: ReactiveEffectOptions
): ReactiveEffect<T> {
  const effect = function reactiveEffect(): unknown {
    if (!effect.active) {
      return options.scheduler ? undefined : fn()
    }
    if (!effectStack.includes(effect)) {
      cleanup(effect)
      try {
        enableTracking()
        effectStack.push(effect)
        activeEffect = effect
        return fn()
      } finally {
        effectStack.pop()
        resetTracking()
        activeEffect = effectStack[effectStack.length - 1]
      }
    }
  } as ReactiveEffect
  effect.id = uid++
  effect.allowRecurse = !!options.allowRecurse
  effect._isEffect = true
  effect.active = true
  effect.raw = fn
  effect.deps = []
  effect.options = options
  return effect
}