先谈谈vue3的响应式实现
vue3通过es6的proxy来实现数据的get和set的劫持。get时将订阅函数加入依赖,set时执行依赖列表。
与Object.defineproperty不同的是,proxy监听的是整个对象,因此可以监听到新增的对象属性。
reactivity的实现
看下面的代码 我们如何实现reactive呢和effect呢?
letproduct=reactive({price:5,quantity:2})lettotal=0effect(()=>{total=product.price*product.quantity})console.log('beforeupdatedquantitytotal='+total)product.quantity=3console.log('afterupdatedquantitytotal='+total)
reactive的功能是数据的get和set的接触,需要使用es6的proxy实现。 effect用来进行依赖收集。
consttargetMap=newWeakMap()//targetMapstorestheeffectsthateachobjectshouldre-runwhenit'supdatedletactiveEffect=null//Theactiveeffectrunningfunctiontrack(target,key){if(activeEffect){//<------ChecktoseeifwehaveanactiveEffect//Weneedtomakesurethiseffectisbeingtracked.letdepsMap=targetMap.get(target)//GetthecurrentdepsMapforthistargetif(!depsMap){//Thereisnomap.targetMap.set(target,(depsMap=newMap()))//Createone}letdep=depsMap.get(key)//Getthecurrentdependencies(effects)thatneedtoberunwhenthisissetif(!dep){//Thereisnodependencies(effects)depsMap.set(key,(dep=newSet()))//CreateanewSet}dep.add(activeEffect)//Addeffecttodependencymap}}functiontrigger(target,key){constdepsMap=targetMap.get(target)//Doesthisobjecthaveanypropertiesthathavedependencies(effects)if(!depsMap){return}letdep=depsMap.get(key)//Iftherearedependencies(effects)associatedwiththisif(dep){dep.forEach((effect)=>{//runthemalleffect()})}}functionreactive(target){consthandler={get(target,key,receiver){letresult=Reflect.get(target,key,receiver)track(target,key)//Ifthisreactiveproperty(target)isGETinsidethentracktheeffecttorerunonSETreturnresult},set(target,key,value,receiver){letoldValue=tarGET@[key]letresult=Reflect.set(target,key,value,receiver)if(result&&oldValue!=value){trigger(target,key)//Ifthisreactiveproperty(target)haseffectstorerunonSET,triggerthem.}returnresult},}returnnewProxy(target,handler)}//对effect函数里的变量都来收集依赖functioneffect(eff){activeEffect=effactiveEffect()activeEffect=null}letproduct=reactive({price:5,quantity:2})lettotal=0effect(()=>{total=product.price*product.quantity})console.log('beforeupdatedquantitytotal='+total)product.quantity=3console.log('afterupdatedquantitytotal='+total)console.log('Updatedquantityto='+product.quantity)
在effect中执行eff, eff中会进行数据的get操作,从而在reactive中的get进行依赖收集。
当更改数据时,执行reactive中的set更新数据。
ref的实现
把上面例子中的total尝试改为用ref包裹的变量。
lettotal=0
那么ref怎么实现呢? 方法一:
functionref(intialValue){returnreactive({value:initialValue})
这种方法使用reactive来实现,也可以直接实现。
functionref(raw){constr={getvalue(){track(r,'value')returnraw},setvalue(newVal){raw=newValtrigger(r,'value')},}returnr}
完整demo如下。
consttargetMap=newWeakMap()//targetMapstorestheeffectsthateachobjectshouldre-runwhenit'supdatedletactiveEffect=null//Theactiveeffectrunningfunctiontrack(target,key){if(activeEffect){//<------ChecktoseeifwehaveanactiveEffect//Weneedtomakesurethiseffectisbeingtracked.letdepsMap=targetMap.get(target)//GetthecurrentdepsMapforthistargetif(!depsMap){//Thereisnomap.targetMap.set(target,(depsMap=newMap()))//Createone}letdep=depsMap.get(key)//Getthecurrentdependencies(effects)thatneedtoberunwhenthisissetif(!dep){//Thereisnodependencies(effects)depsMap.set(key,(dep=newSet()))//CreateanewSet}dep.add(activeEffect)//Addeffecttodependencymap}}functiontrigger(target,key){constdepsMap=targetMap.get(target)//Doesthisobjecthaveanypropertiesthathavedependencies(effects)if(!depsMap){return}letdep=depsMap.get(key)//Iftherearedependencies(effects)associatedwiththisif(dep){dep.forEach((effect)=>{//runthemalleffect()})}}functionreactive(target){consthandler={get(target,key,receiver){letresult=Reflect.get(target,key,receiver)track(target,key)//Ifthisreactiveproperty(target)isGETinsidethentracktheeffecttorerunonSETreturnresult},set(target,key,value,receiver){letoldValue=tarGET@[key]letresult=Reflect.set(target,key,value,receiver)if(result&&oldValue!=value){trigger(target,key)//Ifthisreactiveproperty(target)haseffectstorerunonSET,triggerthem.}returnresult},}returnnewProxy(target,handler)}//对effect函数里的变量都来收集依赖functioneffect(eff){activeEffect=effactiveEffect()activeEffect=null}functionref(raw){constr={getvalue(){track(r,'value')returnraw},setvalue(newVal){raw=newValtrigger(r,'value')},}returnr}letproduct=reactive({price:5,quantity:2})lettotal=ref()effect(()=>{total.value=product.price*product.quantity})console.log('beforeupdatedquantitytotal='+total.value)product.quantity=3console.log('afterupdatedquantitytotal='+total.value)console.log('Updatedquantityto='+product.quantity)
computed的实现
computed的实现,依赖于参数的执行结果。
computed的用法:
lettotal=computed(()=>{returnproduct.price*product.quantity})
computed的实现依赖于ref:
functioncomputed(getter){letresult=ref()effect(()=>(result.value=getter()))returnresult}
完整demo:
consttargetMap=newWeakMap()//targetMapstorestheeffectsthateachobjectshouldre-runwhenit'supdatedletactiveEffect=null//Theactiveeffectrunningfunctiontrack(target,key){if(activeEffect){//<------ChecktoseeifwehaveanactiveEffect//Weneedtomakesurethiseffectisbeingtracked.letdepsMap=targetMap.get(target)//GetthecurrentdepsMapforthistargetif(!depsMap){//Thereisnomap.targetMap.set(target,(depsMap=newMap()))//Createone}letdep=depsMap.get(key)//Getthecurrentdependencies(effects)thatneedtoberunwhenthisissetif(!dep){//Thereisnodependencies(effects)depsMap.set(key,(dep=newSet()))//CreateanewSet}dep.add(activeEffect)//Addeffecttodependencymap}}functiontrigger(target,key){constdepsMap=targetMap.get(target)//Doesthisobjecthaveanypropertiesthathavedependencies(effects)if(!depsMap){return}letdep=depsMap.get(key)//Iftherearedependencies(effects)associatedwiththisif(dep){dep.forEach((effect)=>{//runthemalleffect()})}}functionreactive(target){consthandler={get(target,key,receiver){letresult=Reflect.get(target,key,receiver)track(target,key)//Ifthisreactiveproperty(target)isGETinsidethentracktheeffecttorerunonSETreturnresult},set(target,key,value,receiver){letoldValue=tarGET@[key]letresult=Reflect.set(target,key,value,receiver)if(result&&oldValue!=value){trigger(target,key)//Ifthisreactiveproperty(target)haseffectstorerunonSET,triggerthem.}returnresult},}returnnewProxy(target,handler)}//对effect函数里的变量都来收集依赖functioneffect(eff){activeEffect=effactiveEffect()activeEffect=null}functionref(raw){constr={getvalue(){track(r,'value')returnraw},setvalue(newVal){raw=newValtrigger(r,'value')},}returnr}functioncomputed(getter){letresult=ref()effect(()=>(result.value=getter()))returnresult}letproduct=reactive({price:5,quantity:2})lettotal=computed(()=>{returnproduct.price*product.quantity})console.log('beforeupdatedquantitytotal='+total.value)product.quantity=3console.log('afterupdatedquantitytotal='+total.value)console.log('Updatedquantityto='+product.quantity)
参考资料
https://github.com/Code-Pop/vue-3-reactivity
https://www.bilibili.com/video/BV1SZ4y1x7a9?from=search&seid=16256770774635613102&spm_id_from=333.337.0.0
https://v3.cn.vuejs.org/api/basic-reactivity.html#reactive
https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/WebExtensions/API/proxy