浅谈 vue3 响应式原理 第三篇 ref、computed
能够坚持看到这里,实属不易,vue3 响应式的原理在前两篇已经讲得差不多了。
本篇作为一个补充,结合前面的内容,带大家了解 vue3 开放的2个 api:
回想起上一篇,我们把 Proxy 封装成了 Reactive 函数
通过它,我们对对象的读取、写入进行拦截,在拦截中插入记录和重放的操作,就能把普通的 javascript 对象变成响应式对象。
那么如果我们需要让 javascript 基本类型也实现响应式该怎么办呢?我们写一个 ref 函数。
这个 ref 函数期待接收一个基本类型,并借助 reactive 包一下,然后返回。
| 12
 3
 4
 5
 
 | function ref(raw:any){let result = reactive({})
 result.value = raw
 return result
 }
 
 | 
这样,ref 函数就实现了。
不过 vue3 的 ref 不是这样实现的。
为了区分开基本类型和对象,方便框架后续的维护,vue3 的 ref 采用了独立的实现,并不是使用 Proxy,而是采用存取器属性做拦截。
| 12
 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
 }
 
 | 
说完 ref,我们来说 computed。
computed 实际上是对 effect 的其中一种封装。将借助副作用实现自动计算这个概念写成一个函数,他就是 computed
computed函数 期待接收一个计算函数:getter
getter 会惰性地返回一个值,我们会创建一个响应式对象,将 getter 和响应式对象组合成我们的副作用,通过 effect 进行 track。
getter 中出现的所有响应式对象,都会通过 effect 把这个副作用添加到它们各自的 dep
| 12
 3
 4
 5
 6
 7
 
 | function computed(getter:any){let result = ref()
 effect(()=>{
 result.value = getter()
 })
 return result
 }
 
 | 
| 12
 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)
 product.quantity = 4
 console.log(total.value,salePrice.value)
 product.price = 3
 console.log(total.value,salePrice.value)
 
 | 
这些就是对 vue3 响应式原理比较简单的理解,实际上这些代码和 vue3 源码还是差距很大的,不过只要是能帮助我们学习响应式,这就是值得的。
浅谈 vue3 响应式原理 第一篇 响应式和副作用
浅谈 vue3 响应式原理 第二篇 记录与重放