vue3.2 reactive原理源码解析

240 阅读3分钟

建议先看完 ref响应式原理
先看一下reactive基本的使用

const obj = { name: 'loookooo' }
const state = reacitve(obj)
const fn = () => {
    console.log('name: ', state.name)
}
effect(fn)
state.name = 'window'

这里拆出obj与fn为了方便下文的介绍。
obj经过reactive处理会返回响应式对象Proxy(会在reactiveMap中存储起来),这个Proxy在get时会进行依赖收集,set时会触发依赖,当然还有has,ownKeys,deleteProperty等的处理。细节可看 handlers API 解析
当effect函数执行时,fn(副作用)首次执行,获取state.name时进行依赖收集,并把对应关系存储在targetMap(用来存放所有reactive对应依赖关系的WeakMap)中。
结构长这样: targetMap = { state:{ name:[ fn ] } }
当我们去改变state.name时,触发依赖,则会去targetMap中找到对应的副作用并遍历执行,如上面的[ fn ]就会被遍历执行。
有了基本的认识之后,我们根据例子再来细看每一步的操作。
可与reactive 相关API, effect 相关API一起看。


首先创建响应式对象
const state = reacitve(obj)

export const reactiveMap = new WeakMap<Target, any>() //用来存储经过reactive API转换过的target
export function reactive(target: object) {

  ...
  
  return createReactiveObject(
    target,
    false,
    mutableHandlers, //处理普通对象与数组
    mutableCollectionHandlers, //处理集合结构如Map,WeakMap,Set,WeakSet
    reactiveMap
  )
}

调用 createReactiveObject 创建响应式对象并返回。

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {

  ...
  
  //如果转换过则直接返回,reactiveMap中查找
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  ...
  
  const proxy = new Proxy(
    target,
    //这里判断我们用的是 baseHandlers 因为我们传进来的是普通对象。
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers 
  )
  //存储转换后的对应关系
  proxyMap.set(target, proxy)
  return proxy
}

如果想看...可以翻读上面介绍的相关API。
关键句在 new Proxy(target, baseHandlers),baseHandlers即传进来的mutableHandlers。

//这里直接写上对应方法只是为了根据例子来方便介绍,源码中并不是这样,可以看上面介绍的相关API文章
export const mutableHandlers: ProxyHandler<object> = {
  get(target, key, receiver){
      ...
      //获取结果
      const res = Reflect.get(target, key, receiver)
      ...
      //依赖收集
      track(target, TrackOpTypes.GET, key)
      ...
      //如果为对象则进行响应式转换,所以不管对象嵌套多深,只有被用到的才会转换成响应式对象
      if (isObject(res)) {  
          return isReadonly ? readonly(res) : reactive(res)
      }
  },
  set(target,key,value,receiver){
      //获取旧值
        let oldValue = (target as any)[key]
        ...
        //判断是否key存在该对象上, state.name所以这里为true
        const hadKey = ... hasOwn(target, key)
        //获取结果
        const result = Reflect.set(target, key, value, receiver)
        if (target === toRaw(receiver)) {
          if (!hadKey) {
            //key不存在对象上,触发ADD类型依赖
            trigger(target, TriggerOpTypes.ADD, key, value)
          } else if (hasChanged(value, oldValue)) {
            //key存在对象上且新旧值有变更,触发SET类型依赖
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
          }
        }
        return result
  },
  //不作详细介绍
  deleteProperty, // delete操作符
  has, // name in state
  ownKeys // Object.getOwnPropertyNames , Object.getOwnPropertySymbols
}

到这里 reactive(obj)响应式对象创建完毕,我们关注get与set方法,与vue2的Object.defineProperty对比,好处是不用再去递归进行响应式转换,且由于Proxy是对整个对象进行代理,在新增属性时,可自动变成响应式。


const fn = () => { console.log('name: ', state.name) }
effect(fn)

执行effect(fn), 此时trackOpBit = 1
在effect中,会根据fn创建ReactiveEffect对象,并执行其run方法。
run()
    trackOpBit = 10
    fn()
        console.log('name: ', state.name)
            track() //初始化targetMap中state.name对应的dep,此时 n = 0 , w = 0
                trackEffects()
                n = 10,shouldTrack = true
                dep = [ ReactiveEffect ]
                ReactiveEffect.deps = [ dep ]
     n = 0
     trackOpBit = 1

此时完成了依赖收集,并打印出'name: loookooo'.
targetMap = { state: { name: [ ReactiveEffect ] } }


state.name = 'window'

调用响应式对象set方法,触发tirgger,获取对应的dep,遍历执行副作用  
即执行ReactiveEffect.run()
    trackOpBit = 10
    w = 10
    fn()
        console.log('name: ', state.name)
            track()
                trackEffects()
                    n = 10, shouldTrack = false
    n = 0, w = 0
    trackOpBit = 1

此时打印'name: window'