浅谈 vue3 响应式原理 第三篇 ref、computed

浅谈 vue3 响应式原理 第三篇 ref、computed

能够坚持看到这里,实属不易,vue3 响应式的原理在前两篇已经讲得差不多了。

本篇作为一个补充,结合前面的内容,带大家了解 vue3 开放的2个 api:

ref 函数和 computed 函数

ref 和 computed 的由来

回想起上一篇,我们把 Proxy 封装成了 Reactive 函数

通过它,我们对对象的读取、写入进行拦截,在拦截中插入记录重放的操作,就能把普通的 javascript 对象变成响应式对象。

那么如果我们需要让 javascript 基本类型也实现响应式该怎么办呢?我们写一个 ref 函数。

ref

这个 ref 函数期待接收一个基本类型,并借助 reactive 包一下,然后返回。

1
2
3
4
5
function ref(raw:any){
let result = reactive({})
result.value = raw
return result
}

这样,ref 函数就实现了。

不过 vue3 的 ref 不是这样实现的。

为了区分开基本类型对象,方便框架后续的维护,vue3 的 ref 采用了独立的实现,并不是使用 Proxy,而是采用存取器属性做拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
function ref(raw?:any){
const r = {
get value(){
track(r,'value')
return raw
}
set value(newVal:any){
raw = newVal
trigger(r,'value')
}
}
return r
}

computed

说完 ref,我们来说 computed。

computed 实际上是对 effect 的其中一种封装。将借助副作用实现自动计算这个概念写成一个函数,他就是 computed

computed函数 期待接收一个计算函数:getter
getter 会惰性地返回一个值,我们会创建一个响应式对象,将 getter响应式对象组合成我们的副作用,通过 effect 进行 track
getter 中出现的所有响应式对象,都会通过 effect 把这个副作用添加到它们各自的 dep

1
2
3
4
5
6
7
function computed(getter:any){
let result = ref()
effect(()=>{
result.value = getter()
})
return result
}

整合3篇文章内容后的响应式代码

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
const targetMap = new WeakMap()
let activeEffect:any = null

function track(target:any,key:string){
if(!activeEffect){
return
}
let depsMap = targetMap.get(target)
if(!depsMap){
targetMap.set(target,(depsMap = new Map()))
}
let dep = depsMap.get(key)
if(!dep){
depsMap.set(key,(dep = new Set()))
}
dep.add(activeEffect)
}

function trigger(target:any,key:string){
const depsMap = targetMap.get(target)
if(!depsMap){
return
}
const dep = depsMap.get(key)
if(dep){
dep.forEach((effect:any)=>{
effect()
})
}
}

function reactive(target:any){
const handler:any = {
get(target:any,key:string,receiver:any){
let result = Reflect.get(target,key,receiver)
track(target,key)
return result
},
set(target:any,key:string,value:any,receiver:any){
let oldVal = target[key]
let result = Reflect.set(target,key,value,receiver)
if(result && oldVal !== value){
trigger(target,key)
}
return result
}
}
return new Proxy(target,handler)
}

function effect(eff:any){
activeEffect = eff
activeEffect()
activeEffect = null
}

function ref(raw?:any){
const r = {
get value(){
track(r,'value')
return raw
},
set value(newVal:any){
raw = newVal
trigger(r,'value')
}
}
return r
}

function computed(getter:any){
let result = ref()
effect(()=>{
result.value = getter()
})
return result
}

let p = {
price:2,
quantity:3
}

let product = reactive(p)
let salePrice = computed(()=>{
return product.price * 0.9
})
let total = computed(()=>{
return salePrice.value * product.quantity
})

console.log(total.value,salePrice.value)//5.4,1.8
product.quantity = 4
console.log(total.value,salePrice.value)//7.2,1.8
product.price = 3
console.log(total.value,salePrice.value)//10.8,2.7

这些就是对 vue3 响应式原理比较简单的理解,实际上这些代码和 vue3 源码还是差距很大的,不过只要是能帮助我们学习响应式,这就是值得的。

浅谈 vue3 响应式原理 第一篇 响应式和副作用
浅谈 vue3 响应式原理 第二篇 记录与重放