# Vue3 ref 响应式源码
问题探索:
ref
接受的原始数据是什么类型?是原始值还是引用值,还是都行?返回的响应式数据本质具体是什么?根据传递的数据类型不同,返回的响应式对象是否不同?
响应式数据改变会触发界面更新,那原始数据改变会触发界面更新吗?即原始数据和返回的响应式数据是否有关联
先看一下ref的使用
const myName = ref('false') // 一个 RefImpl 实例
const fn = () => {
console.log(myName);
}
effect(fn)
myName.value = 'new-loookooo'
1
2
3
4
5
6
2
3
4
5
6
打开控制台,打印出了RefImpl
实例: myName
,具有以下一系列数据:
我们来逐步分析 ref 的源码实现,并适当的简化了一些干扰项。
# ref 的构造器(本质是观察者模式的实践)
function ref(value) {
if (isRef(rawValue)) { // 判断是否是 ref
return rawValue;
}
return new RefImpl(rawValue);
}
// 创建 RefImpl 类的,这里我们按照默认深度监听来
class RefImpl {
constructor(value, __v_isShallow=false) {
this.dep = undefined; // dep 依赖
this.__v_isRef = true; // 标识是否为ref类型
this._rawValue = __v_isShallow ? value : toRaw(value); // 用于保存未转换前的原生数据,toRaw()将响应式对象转化为普通对象
this._value = __v_isShallow ? value : toReactive(value); // 因为无法监听原始值,所以包装成 reactive (proxy响应式对象)
}
get value() {
trackRefValue(this); // 依赖收集
return this._value;
}
set value(newVal) {
newVal = __v_isShallow ? value : toRaw(newVal); // 先将值转化为原始值
if (shared.hasChanged(newVal, this._rawValue)) { // 判断前后值是否改变
this._rawValue = newVal;
this._value = this.__v_isShallow ? newVal : toReactive(newVal);
triggerRefValue(tish, newVal); // 依赖更新
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 依赖收集和依赖更新
接下来我们看下监听器里的 trackRefValue
和 triggerRefValue
是如何工作的。
// 全局属性,是否可以收集依赖
let shouldTrack = true;
// 全局属性,储存当前 effect
let activeEffect= void 0;
function trackRefValue(ref) { // 依赖收集
if (shouldTrack && activeEffect) { // 判断是否可以收集依赖
ref = toRaw(ref);
trackEffects(ref.dep);
}
}
function triggerRefValue(ref, newVal) { // 依赖更新
ref = toRaw(ref);
triggerEffects(ref.dep);
}
export function trackEffects(dep) {
// 用 dep 来存放所有的 effect,为一个Set数组,且有 n 和 w 的标记
// TODO
// 这里是一个优化点
// 先看看这个依赖是不是已经收集了,
// 已经收集的话,那么就不需要在收集一次了
// 可能会影响 code path change 的情况
// 需要每次都 cleanupEffect
// shouldTrack = !dep.has(activeEffect!);
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
(activeEffect as any).deps.push(dep);
}
}
function triggerEffects(dep) {
// 执行收集到的所有的 effect 的 run 方法
for (const effect of dep) {
if (effect.scheduler) {
// scheduler 可以让用户自己选择调用的时机
// 这样就可以灵活的控制调用了
// 在 runtime-core 中,就是使用了 scheduler 实现了在 next ticker 中调用的逻辑
effect.scheduler();
} else {
effect.run();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2.详细分析
// 静态值定义
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 ] 就不用讲了,数据改变时触发所关联的副作用(依赖)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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
则不会在进行依赖收集。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
上面的例子中介绍了 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
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
场景 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() 同理
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
场景 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
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58