实现ref
上篇文章实现多个对象对应的依赖的收集 (opens new window)已经实现了Vue3提供的响应式APIreactive
,同时我们也发现了没有办法代理基本类型。也就是Vue3提供的另一个响应式相关的APIref
,接下来我们来实现下:
// 基本类型的响应式API 类似于 Object.defineProperty
function ref(raw) {
const target = {
get value() {
console.log('基本类型----收集依赖')
track(target, 'value')
return raw
},
set value(newValue) {
console.log('基本类型----触发依赖')
raw = newValue
trigger(target, 'value')
}
}
return target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以看到,其实ref
的实现思路,也是类似于Object.defineProperty
做的事情:
- 设置
set
、get
的拦截器。在读取属性的时候收集依赖,设置属性的时候触发依赖。只不过,key
值统一都是value
。
结合之前的实现,完整实现如下:
// 使用 WeakMap 描述多个对象的多个属性对应多个依赖的对应关系
const targrtMap = new WeakMap()
// 使用 activeEffect 变量保存当前激活的 effect
let activeEffect = null
// 引用类型的代理函数
function reactive(target) {
const handler = {
get: function(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 收集依赖
track(target, key)
return res
},
set: function(target, key, value) {
let oldValue = target[key]
let res = Reflect.set(target, key, value)
// 这里触发依赖
if (res && oldValue !== value) {
trigger(target, key)
}
return res
}
}
const proxy = new Proxy(target, handler)
return proxy
}
// 基本类型的响应式API 类似于 Object.defineProperty
function ref(raw) {
const target = {
get value() {
console.log('基本类型----收集依赖')
track(target, 'value')
return raw
},
set value(newValue) {
console.log('基本类型----触发依赖')
raw = newValue
trigger(target, 'value')
}
}
return target
}
// 依赖收集函数
function track(target, key) {
if (!activeEffect) return
let depsMap = targrtMap.get(target)
if (!depsMap) {
targrtMap.set(target, depsMap = new Map())
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, dep = new Set())
}
// 收集依赖
dep.add(activeEffect)
}
// 触发依赖函数
function trigger(target, key) {
let depsMap = targrtMap.get(target)
if (!depsMap) return
let dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
// 副作用函数
function effect(eff) {
activeEffect = eff
activeEffect()
activeEffect = null
}
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
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
我们写个例子测试一下:
// 测试例子
let total = 0
let total1 = 0
let discount = 0.8
let product = {
price: 10,
quantity: 2
}
let goods = {
price: 10,
quantity: 2
}
const proxyProduct = reactive(product)
const proxyGoods = reactive(goods)
const refDiscount = ref(discount)
// 这个就是副作用函数
effect(() => {
total = proxyProduct.quantity * proxyProduct.price * (refDiscount.value)
})
effect(() => {
total1 = proxyGoods.price * proxyGoods.quantity * (refDiscount.value)
})
proxyProduct.price = 500
proxyGoods.quantity = 500
console.log('product的total的值为:', total) // 800
console.log('goods的total1的值为:', total1) // 4000
// 修改了 discount 的代理值 此时也能自动执行了
refDiscount.value = 0.5
console.log('product的total的值为:', total) // 500
console.log('goods的total1的值为:', total1) // 2500
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
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
当我们修改refDiscount
的值,也能执行依赖函数了。所以total
和total1
的值也相应的更新了。同时,Vue3提供的ref
API也支持对对象进行代理,我们前面已经实现了对象的代理函数reactive
,所以实现思路也很简单,就是如果代理的是对象,就使用reactive
函数进行代理:
const isObject = (val) => val !== null && typeof val === 'object
function ref(raw) {
// 新增 如果是对象 就使用`reactive`函数
if (isObject(raw)) {
return reactive(raw)
}
const target = {
get value() {
track(target, 'value')
return raw
},
set value(newValue) {
raw = newValue
trigger(target, 'value')
}
}
return target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
然后我们将我们的测试例子修改下,都使用ref
进行代理:
// 测试例子
let total = 0
let total1 = 0
let discount = 0.8
let product = {
price: 10,
quantity: 2
}
let goods = {
price: 10,
quantity: 2
}
const proxyProduct = ref(product)
const proxyGoods = ref(goods)
const refDiscount = ref(discount)
// 这个就是副作用函数
effect(() => {
total = proxyProduct.quantity * proxyProduct.price * (refDiscount.value)
})
effect(() => {
total1 = proxyGoods.price * proxyGoods.quantity * (refDiscount.value)
})
proxyProduct.price = 500
proxyGoods.quantity = 500
console.log('product的total的值为:', total) // 800
console.log('goods的total1的值为:', total1) // 4000
// 修改了 discount 的代理值 此时也能自动执行了
refDiscount.value = 0.5
console.log('product的total的值为:', total) // 500
console.log('goods的total1的值为:', total1) // 2500
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
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
可以看到,ref
也正常代理了我们的引用类型。这样,我们就把Vue3中很重要的两个响应式API reactive
、ref
给实现了,虽然我们的实现非常粗糙,但是和vue3的实现思路基本是一致的。如果你理解了之前几篇文章的实现,那么你去阅读Vue3的源码想必是有恍然大悟的感觉的,因为我们的实现基本覆盖了vue3响应式模块的主线思想。
编辑 (opens new window)
上次更新: 2021/10/30, 16:48:40