先看一下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 value和set 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
*/