vue 3.2.26 源码解读(一)reactivity响应式

·  阅读 372

vue 3.2.26 源码解读(一)reactivity响应式
vue 3.2.26 源码解读(二)初始化渲染
vue 3.2.26 源码解读(三)diff算法原理

总体流程图

流程.jpg

demo

带着两个问题来看下面这段代码

  1. reactive的返回值是什么?
  2. effect怎么感知到内部响应式数据的变化?
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)
})

带着这两个问题我们来看代码中的核心逻辑

reactive 方法

  1. 判断是否只读
  2. 调用createReactiveObject返回响应式对象
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
  ) 
}

createReactiveObject

  1. 校验代理元对象是否合法
  2. 使用全局proxyMap保存代理对象,以便下次调用
  3. 使用Proxy代理
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  //... 校验逻辑逻辑
  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
}

以上代理逻辑主要写在baseHandlers中代理普通对象,我们先按照顺序一步一步看代码是怎么执行的。

effect 方法

  1. 创建ReactiveEffect
  2. 判断是有options配置且是否lazy决定是否首次调用ReactiveEffect中的run方法
  3. 指定run方法中的this指向
export function effect<T = any>(
  fn: () => T,
  options?: ReactiveEffectOptions
): ReactiveEffectRunner {

  //... 校验逻辑省略
  
  const _effect = new ReactiveEffect(fn)
  
  //... 非主线逻辑省略
  
  if (!options || !options.lazy) {
    _effect.run()
  }
  const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
  runner.effect = _effect
  return runner
}

ReactiveEffect

  1. 构造函数constructor中传入三个参数fn scheduler, scope, 我们重点关注第一个参数
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined

export class ReactiveEffect<T = any> {
  active = true
  deps: Dep[] = []

  // can be attached after creation
  computed?: boolean
  allowRecurse?: 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 | 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
    }
  }
}

run方法中核心逻辑:

  • 将当前effect赋值给activeEffect并推入effectStack
  • 执行fn,即用户传入的函数。
  • 重置activeEffecteffectStack状态

当我们执行fn时,我们会触发ar对象的get操作,接下来我们来看看这里做了什么

effect(() => (
    dummy = ar.bbb
))

baseHandlers get

  1. 获取当前key对应的原始对象值
  2. 跟踪当前key的依赖
  3. 判断原始值是否为对象,是否需返回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
  }
}

track依赖跟踪

  1. 创建一个依赖对照表
  • 每个obj对应一个依赖WeekMap
  • 对象中每个key对应一个Set
  • Set中存储当前key对应的effect

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 const createDep = (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) 
}

以上依赖收集过程结束,接下来我们再来看看当我们更新时的逻辑

baseHandlers set

  1. 比对新老值,判断是否新增属性
  2. 若是修改,判断新老值是否一致,走不同的trigger逻辑
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
  }
}

trigger触发更新

  1. 获取当前依赖Map
  2. 根据keyMap上获取对应的effects
  3. 遍历执行effects,判断effects上是否有scheduler,若有则执行scheduler否则执行run方法。
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()
      }
    }
  }
}

最后我们再来回顾这张图看看有没有清晰很多:

流程.jpg

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改