浅谈 vue3 响应式原理 第三篇 ref、computed
能够坚持看到这里,实属不易,vue3 响应式的原理在前两篇已经讲得差不多了。
本篇作为一个补充,结合前面的内容,带大家了解 vue3 开放的2个 api:
回想起上一篇,我们把 Proxy 封装成了 Reactive 函数
通过它,我们对对象的读取、写入进行拦截,在拦截中插入记录和重放的操作,就能把普通的 javascript 对象变成响应式对象。
那么如果我们需要让 javascript 基本类型也实现响应式该怎么办呢?我们写一个 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 }
|
说完 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 }
|
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) product.quantity = 4 console.log(total.value,salePrice.value) product.price = 3 console.log(total.value,salePrice.value)
|
这些就是对 vue3 响应式原理比较简单的理解,实际上这些代码和 vue3 源码还是差距很大的,不过只要是能帮助我们学习响应式,这就是值得的。
浅谈 vue3 响应式原理 第一篇 响应式和副作用
浅谈 vue3 响应式原理 第二篇 记录与重放