vue3.2 ref响应式原理源码解析

668 阅读7分钟

先看一下ref的使用

const myName = ref('loookooo')  
const fn = () => {
    console.log('say myName: ', myName.value);
}
effect(fn)
myName.value = 'new-loookooo'
ref 响应式
我们先带大家走一遍 ref 响应式的过程。再通过例子具体分析。
首先创建一个ref响应式对象myName,
这个myName有一个value的属性用来存储我们传进去的loookooo字符串
这个响应式对象在获取value时会收集依赖,设置value时会触发依赖 (这里所说的依赖其实就是副作用effect)
执行effect Api 并将我们定义的fn传进去
然后执行一遍fn,进行依赖收集,把fn记录在 myName 这个响应式对象里面
当我们去修改 myName.value 时,则会触发依赖,执行fn
tip: 
    ref如果传入一个对象,则会把value 转换成 reactive 响应式对象

我们接着来逐步分析ref的源码实现。
我们只看该实现的关键地方,详细的可以去看另一篇文章关于ref API 的源码分析。
vue3.2对ref进行了标记优化,避免依赖的重复收集,针对该优化会在后面做场景分析。

const myName = ref('loookooo')

ref('loookooo') -> createRef('loookooo',false) -> new RefImpl('loookooo', false)

//创建 RefImpl 类的实例 myName
myName: RefImpl = {

    _value = 'loookooo'
    _rawValue = 'loookooo'
    dep = undefined
    __v_isRef = true

    get value() {
        ...
        trackRefValue(this) //收集依赖
        ...
    }
    set value(newVal) {
        ...
        triggerRefValue(this, newVal)
        ...
    }
}

Read: 
    此时 myName 等于一个由 RefImpl 类创建出来的实例,且有一个get valueset value方法,
    获取 myName.value 时,调用 trackRefValue 收集依赖,
    设置 myName.value 时,调用 triggerRefValue 触发依赖。

const fn = () => { console.log('say myName: ', myName.value); }
effect(fn)

effectStack: ReactiveEffect[]   // 响应式副作用栈
activeEffect: ReactiveEffect    // 当前活动副作用
effectTrackDepth: number = 0  // 副作用收集的深度
maxMarkerBits:number = 30  //最大深度30
trackOpBit: number = 1   // 二进制表示

// 执行 effect(fn)
effect(fn) {
    ...
    _effect = new ReactiveEffect(fn)
    ...
    _effect.run()
    ...
}
// 创建 ReactiveEffect 类实例 _effect 并传入 fn
_effect: ReactiveEffect = {

    active = true
    deps: Dep[] = []

    run() {
        ...
        activeEffect = this     // 让 activeEffect 等于当前 ReactiveEffect
        effectStack.push(activeEffect)  //压入栈
        ...
        trackOpBit = 1 << ++effectTrackDepth  // effectTrackDepth = 1,trackOpBit = 10(二进制)
        ...
        initDepMarkers(this) 
        // initDepMarkers 是对 Reactive.deps中的每个dep的 w 标记进行初始化,
        // 运算逻辑为 deps[i].w |= trackOpBit 
        ...
        this.fn()   //fn执行
        ...
        finalizeDepMarkers(this)
        ...
        trackOpBit = 1 << --effectTrackDepth  // effectTrackDepth = 0,trackOpBit = 1(二进制)
        ...
        effectStack.pop()   //栈弹出 activeEffect
        ...
        const n = effectStack.length // n = 0
        activeEffect = n > 0 ? effectStack[n - 1] : undefined // activeEffect = undefined
    }
}

finalizeDepMarkers(effect) {
    const { deps } = effect
    ...遍历 deps 取出每个 dep
    ...如果dep是重复的收集且不是重新的收集,则删除dep中对应当前的effect
    if (wasTracked(dep) && !newTracked(dep)) {
        dep.delete(effect)
    }
    dep.w &= ~trackOpBit // w 标记回归
    dep.n &= ~trackOpBit // n 标记回归
}


    // fn 执行,myName.value 被使用,触发依赖收集

    RefImpl.get() -> trackRefValue(this)

    trackRefValue(ref) {
        ...
        ref.dep = createDep() // RefImpl.dep 被初始化, dep为一个Set,且有 n 和 w 的标记
        ...
        trackEffects(ref.dep)
    }

    trackEffects(dep) {
        ...
        if (!newTracked(dep)) {
            dep.n |= trackOpBit  // dep.n = 10
            shouldTrack = !wasTracked(dep)
        }
        ...
        if(shouldTrack) {
            ...
            dep.add(activeEffect)  // RefImpl.dep = [ activeEffect ]
            activeEffect!.deps.push(dep)  // ReactiveEffect.deps = [ RefImpl.dep ]
            ...
        }
    }


Read: 
    activeEffect 指向当前的正在操作的副作用 (由ReactiveEffect类创建出来的实例)
    也可以理解为就是effect传进去的方法,只不过用ReactiveEffec把它包装起来,在调用时做一些处理操作

    effectStack 副作用栈,当出现递归调用时,会把每个副作用压入栈中。
    例如嵌套场景: effect(() => {  effect(()=>{})  })

    effectTrackDepth  副作用收集的深度,当出现递归调用时,记录深度。默认值 0
    如上的嵌套场景,当执行到第二个effect时,effectTrackDepth = 2,即深度等于2。

    maxMarkerBits 最大深度,指effectTrackDepth的最大边界。默认值 30。
    正常使用是不会出现超过30层的情况,所以关于深度边界的判断我们先忽略。
    当effectTrackDepth超出边界时,会调用cleanupEffect来清空所有响应式对应的当前副作用
    例如:ReactiveEffect.deps = [ RefImpl.dep ],此时 RefImpl.dep.delete(ReactiveEffect)

    trackOpBit 默认值为1,二进制表示,通过对effectTrackDepth的位运算来处理 dep 的 n 与 w 标记
    dep.n 代表是否是重新的收集
    dep.w 代表是否已经收集过
    这两个都是标记值,需要与 trackOpBit 进行与运算得出结果。

    首先执行effect(fn), 用 fn 产出一个 ReactiveEffect,并执行ReactiveEffect.run方法
    ReactiveEffect.run 执行,先让 activeEffect 等于 ReactiveEffect, 且压入effectStack栈中。
    effectTrackDepth 加 1,并通过位运算得出 trackOpBit。
    此时 
        activeEffect = ReactiveEffect
        effectStack = [ ReactiveEffect ]
        effectTrackDepth = 1
        trackOpBit = 10(二进制表示)
        
    initDepMarkers 执行初始化 dep.w (当前 ReactiveEffect.deps 为空,所以并不会有任何操作)
    这个时候才到了fn()的执行。
    在fn中我们打印了 console.log('say myName: ', myName.value);
    调用了 myName 的 get value(), trackRefValue 进行依赖收集。
    trackRefValue 中会初始化 RefImpl.dep,并调用trackEffects进行真正的依赖收集。
    这里的初始化就是让dep = new Set(), 且给 dep 加上 n 和 w 的标记,即dep.n, dep.w,初始值为0。
    在这里初始化的好处是,只有当需要进行依赖收集的RefImpl,才会给RefImlp.dep 开拓空间。
    初始化完dep后执行 trackEffects 并传入刚初始化完的dep
    
    trackEffects中有一个 shouldTrack 来决定是否需要进行收集,默认值为false。
    newTracked(dep) 判断是否重新收集 dep.n & trackOpBit > 0
    wasTracked(dep) 判断是否被收集过 dep.w & trackOpBit > 0
    首先判断是否是重新的收集。
    当不是重新的收集时: 
        给 dep.n 进行 dep.n |= trackOpBit 赋值
        并且对 shouldTrack 进行更新, shouldTrack = !wasTracked(dep)
    然后依据shouldTrack再决定是否进行依赖收集。
    根据上面的例子:此时 shouldTrack = true,所以会进行依赖收集
    则双向依赖:
        dep = [ ReactiveEffect ]
        ReactiveEffect.deps = [ dep ]
    ReactiveEffect.deps = [ dep ] 是为了可以从 ReactiveEffect 找到它所包含的dep进行一些标记的赋值操作。
    dep = [ ReactiveEffect ] 就不用讲了,数据改变时触发所关联的副作用(依赖)。

myName.value = 'new-loookooo'

    调用 RefImple.set value() ,新旧值判断发生改变,触发依赖
    调用 triggerRefValue -> triggerEffects(RefImple.dep)
    triggerEffects : 遍历 dep 中所关联的副作用,并执行。
    此时 dep = [ ReactiveEffect ]
    ReactiveEffect 就是由 fn 生成的。
    执行 ReactiveEffect.run()
    trackOpBit = 10
    initDepMarkers 执行初始化 dep.w (ReactiveEffect.deps = [ dep ] , dep.w = 10)
    接着执行Reactive.fn()也就是我们定义的fn。
    fn中会触发 get value() -> trackRefValue -> trackEffect
    此时  trackOpBit = 10 ; dep.n = 0 ; dep.w = 10;
    不是重新的收集则 dep.n = 10, 且已经收集过了(dep.w & trackOpBit > 0 = true),
    所以 shouldTrack = false
    则不会在进行依赖收集。

上面的例子中介绍了 n 与 w 标记的如何变化,但可能场景过于单一,在这里,我会区分场景再来分析分析, 记录函数调用,以及记录 trackOpBit, n , w 每次的变化。

场景 1

    const myName = ref('name')
    effect(()=>{
        console.log('one: ', myName.value);
        console.log('two: ', myName.value);
    })
    
    /**
     *  trackOpBit = 1 ; n = 0 ; w = 0
     *  effect()
     *      trackOpBit = 10 ; n = 0 ; w = 0
     *      initDepMarkers()
     *      fn()
     *          console.log('one: ', myName.value);
     *              trackOpBit = 10 ; n = 10 ; w = 0
     *              shouldTrack = true
     *              dep = [ ReactiveEffect ]
     *              ReactiveEffect.deps = [ dep ]
     *          console.log('two: ', myName.value);
     *              shouldTrack = false
     *      finalizeDepMarkers()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *      trackOpBit = 1 ; n = 0 ; w = 0
     */
     
    myName.value = 'newName'
    
    /**
     *  triggerEffects()
     * 
     *      ReactiveEffect.run()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *          initDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 10
     *          fn()
     *              console.log('one: ', myName.value);
     *                  trackOpBit = 10 ; n = 10 ; w = 10
     *                  shouldTrack = false
     *              console.log('two: ', myName.value);
     *                  shouldTrack = false
     *          finalizeDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *          trackOpBit = 1 ; n = 0 ; w = 0
     */

场景 2

    const myName = ref('name')
    effect(()=>{
        console.log('one: ', myName.value);
        effect(()=>{
            console.log('two: ', myName.value);
        })
    })
    
    /**
     *  trackOpBit = 1 ; n = 0 ; w = 0
     *  effect()
     *      trackOpBit = 10 ; n = 0 ; w = 0
     *      initDepMarkers()
     *      fn()
     *          console.log('one: ', myName.value);
     *              trackOpBit = 10 ; n = 10 ; w = 0
     *              shouldTrack = true
     *              dep = [ ReactiveEffect ]
     *              ReactiveEffect.deps = [ dep ]
     *          effect()
     *              trackOpBit = 100 ; n = 10 ; w = 0
     *              initDepMarkers()
     *              fn()
     *                  console.log('two: ', myName.value);
     *                      trackOpBit = 100 ; n = 110 ; w = 0
     *                      shouldTrack = true
     *                      dep = [ ReactiveEffect, ReactiveEffect2 ]
     *                      ReactiveEffect2.deps = [ dep ]
     *              finalizeDepMarkers()
     *                  trackOpBit = 100 ; n = 10 ; w = 0
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *      finalizeDepMarkers()
     *      trackOpBit = 1 ; n = 0 ; w = 0
     */
     
    myName.value = 'newName'
    
    /**
     *  triggerEffects()
     * 
     *      ReactiveEffect.run()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *          initDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 10
     *          fn()
     *              console.log('one: ', myName.value);
     *                  trackOpBit = 10 ; n = 10 ; w = 10
     *                  shouldTrack = false
     *              effect()
     *                  trackOpBit = 100 ; n = 10 ; w = 10
     *                  initDepMarkers()
     *                  fn()
     *                      console.log('two: ', myName.value);
     *                          trackOpBit = 100 ; n = 110 ; w = 10
     *                          shouldTrack = true
     *                          dep = [ ReactiveEffect, ReactiveEffect2, ReactiveEffect3 ]
     *                          ReactiveEffect3.deps = [ dep ]
     *                  finalizeDepMarkers()
     *                      trackOpBit = 100 ; n = 10 ; w = 10
     *                  trackOpBit = 10 ; n = 10 ; w = 10
     *          finalizeDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *          trackOpBit = 1 ; n = 0 ; w = 0
     * 
     *      ReactiveEffect2.run()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *          initDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 10
     *          fn()
     *              console.log('two: ', myName.value);
     *                  trackOpBit = 10 ; n = 10 ; w = 10
     *                  shouldTrack = false
     *          finalizeDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *          trackOpBit = 1 ; n = 0 ; w = 0
     * 
     *      ReactiveEffect3.run()
     *          与 ReactiveEffect2.run() 同理
     */

场景 3

    const myName = ref('name')
    let f = true
    effect(()=>{
        if(f) {
            console.log('one: ', myName.value);
        }
    })
    /**
     *  trackOpBit = 1 ; n = 0 ; w = 0
     *  effect()
     *      trackOpBit = 10 ; n = 0 ; w = 0
     *      initDepMarkers()
     *      fn()
     *          if(true) : console.log('one: ', myName.value);
     *              trackOpBit = 10 ; n = 10 ; w = 0
     *              shouldTrack = true
     *              dep = [ ReactiveEffect ]
     *              ReactiveEffect.deps = [ dep ]
     *      finalizeDepMarkers()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *      trackOpBit = 1 ; n = 0 ; w = 0
     */
     
    myName.value = 'newName'
    
    /**
     *  triggerEffects()
     * 
     *      ReactiveEffect.run()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *          initDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 10
     *          fn()
     *              if(true) : console.log('one: ', myName.value);
     *                  trackOpBit = 10 ; n = 10 ; w = 10
     *                  shouldTrack = false
     *          finalizeDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *          trackOpBit = 1 ; n = 0 ; w = 0
     */
     
    f = false
    myName.value = 'loookooo'
    
    /**
     *  triggerEffects()
     * 
     *      ReactiveEffect.run()
     *          trackOpBit = 10 ; n = 0 ; w = 0
     *          initDepMarkers()
     *              trackOpBit = 10 ; n = 0 ; w = 10
     *          fn()
     *              if(false) : no thing;
     *          finalizeDepMarkers()
     *              dep = []
     *              trackOpBit = 10 ; n = 0 ; w = 0
     *          trackOpBit = 1 ; n = 0 ; w = 0
     */