Vue3响应式原理如有错误请指出~更多学习笔记求戳:https://github.com/6fa/WebKno...1.响应式核心如果在下面的例子中,我希望sum成为响应变量:letnum1=1;让num2=2;让sum=num1+num2;num1=10console.log(sum)//sum还是3,需要实现非响应部分:数据劫持:知道num1和num2什么时候变化依赖收集:知道sum依赖于哪些数据。例子中sum依赖于num1和num2,必须建立它们的依赖关系才能分发更新:当依赖的数据num1和num2发生变化时,要通知响应对象sum重新计算Vue3拦截读取和设置通过代理(数据劫持)的数据。读取数据时,通过track函数触发依赖项的收集;设置数据后,将通过触发器功能调度更新。那么vue3是如何使用响应式的呢?Vue3不仅可以通过data函数返回一个响应式对象,还可以通过ref和reactive创建响应式变量。使用reactive等时,数据在内部用Proxy打包。在使用computed、watch、view渲染函数等时,可以看作是声明了一个依赖响应数据的回调。此回调将传递给效果(副作用函数)。当依赖的数据发生变化时,回调将再次被调用,从而计算等待更新。实现一个简单版本的响应式,它的大体结构是://创建响应式变量,拦截数据get和setfunctionreactive(obj){}//effect函数包装那些依赖响应式数据的函数cb//cb依赖于当数据更新,重新执行效果函数effect(cb){}//依赖收集,建立响应式数据与效果函数的映射关系track(target,property){}//触发更新,根据执行效果函数依赖函数trigger(target,property){}使用:letobj=reactive({num1:10,num2:20})letsum=0effect(()=>{sum=obj.num1+obj.num2})console.log(sum)//30obj.num1=100console.log(sum)//应该是1202的基本使用。Proxy&Reflect在创建响应式变量之前,需要知道Proxy和Reflect的基本使用。JS很难跟踪单个局部变量,但可以跟踪对象的属性变化:vue3使用的ES6的Proxy和Reflect。Proxy拦截读取和设置对象等操作,然后进行操作处理。但是不会直接操作源对象,而是通过对象的代理对象。//代理的用法//代理对象由target(目标对象)和handler(指定代理对象行为的对象)组成lettarget={a:1,b:2}lethandler={//receiver引用调用行为的对象,通常是Proxy实例本身]=value}}letproxy=newProxy(target,handler)console.log(proxy.a)//1proxy.a=3console.log(proxy.a)//3Reflect可以直接调用对象的内部方法,并具有与Proxy操作相同的获取和设置。//体现用法lettarget={geta(){returnthis.num1+this.num2},seta(val){returnthis.num1=val}}//receiver为可选参数letreceiver={num1:10,num2:20}//Reflect.get(target,propKey,receiver)//相当于直接操作target的geta(){}Reflect.get(target,'a',receiver)//30thisbinding当说到receiver//Reflect.set(target,propKey,value,receiver)Reflect.set(target,'a',100,receiver)//100Reflect主要是解决this的绑定问题,将this绑定到代理对象上,而不是目标对象:比如Reflect.get(target,property,receiver)获取一个属性时,如果该属性指定了一个getter,则getter的this会绑定到receiver对象上。//代理问题constobj={a:10,getdouble(){returnthis.a*2}}constproxyobj=newProxy(obj,{get(target,propKey,receiver){returntarget[propKey]}})letobj2={__proto__:proxyobj}obj2.a=20obj2.double//预期值为40,实际为20,因为doublegetter中的this绑定了obj//使用Reflect来解决这个绑定问题constobj={a:10,getdouble(){returnthis.a*2}}constproxyobj=newProxy(obj,{get(target,propKey,receiver){//这里的接收者是obj2returnReflect.get(target,propKey,receiver)}})letobj2={__proto__:proxyobj}obj2.a=20obj2.double//40、通过Refelct的receiver,getdouble()中的this绑定obj23.Reactive函数的实现来创建响应式变量响应式函数的实现主要依赖内部实例化一个Proxy对象:functionreactive(obj){consthandler={//拦截数据get和set进行处理get(){},set(){}}constproxyObj=newProxy(obj,handler)返回nproxyObj//返回代理对象实例}获取数据时,需要启动数据依赖收集(交给track函数实现);设置数据时,需要触发更新(交给触发函数来实现):functionreactive(obj){consthandler={get(target,propKey,receiver){constval=Reflect.get(...arguments)//读取数据track(target,propKey)//依赖集合returnval},set(target,propKey,newVal,receiver){constsuccess=Reflect.set(...arguments)//设置数据,返回true或falsetrigger(target,propKey)//触发更新返回成功}}constproxyObj=newProxy(obj,handler)returnproxyObj//返回代理对象实例}但是上面只针对一层对象有效,但对属性或对象的多层嵌套对象无效,需要手动递归实现响应:functionreactive(obj){consthandler={get(target,propKey,receiver){constval=Reflect.get(...arguments)track(target,propKey)if(typeofval==='object'){returnreactive(val)//New}returnval}}...}4.侧面effect函数实现了上面的track触发前,需要先了解sideeffect函数。sideeffectfunction用于跟踪正在运行的函数,比如watch和computed。其中的代码将被传递生效。当依赖于watch和computed的其他数据发生变化时,它内部会重新运行。代码。以vue3的computed为例:constnum=ref(10)//num是响应式的constdouble=computed(()=>num*2)可以把computed中的内容看做是一个依赖响应式数据的更新函数(以下称为update函数),而computed返回一个ref引用,那么在computed函数内部,会有类似下面的操作:computed(cb){constresult=ref()effect(()=>result.value=cb())returnresult}update函数作为effect函数的回调://当前运行的sideeffectfunctionletactiveEffect=nullconsteffect=(cb)=>{activeEffect=cb//运行responsivefunctioncb()activeEffect=null}effect函数执行更新函数,它会读取它所依赖的数据。我们之前已经为这些数据设置了proxy代理,此时已经完成了依赖收集(建立update函数和依赖数据的映射关系,当数据发生变化时,依赖的update函数数据将通过映射关系找到并再次执行)。拿下面的例子来说明:letnum=reactive({value:10})letdouble=0effect(()=>{double=num.value*2})//double是reactivelettriple=num.value*3//三元组不是反应性的//1。num被reactive封装为一个reactive变量,会拦截其属性的获取和设置//2.运行副作用函数effect,将当前运行的副作用函数activeEffect指向effect的回调,即update函数//3。执行activeEffect(更新函数)//4.update函数会读取num.value并触发proxy的依赖收集跟踪函数//6.track会将num.value映射到updatefunction关系//建立映射关系的原因是当num.value发生变化时,trigger需要找出所有依赖于num.value的updatefunction//然后重新-运行所有这些,以便重新分配double//7.double已分配//8.将activeEffect重新指向null//8.运行到triple,即使读取了num.value,但是此时activeEffect为null//不会建立num.value和activeEffect的映射关系,所以num.value不会改变,会更新到执行triple5.Track&Triggertrack(target,property):主要是把target.property和update函数记录在一起,形成映射关系,这样就可以知道target.property有哪些update函数依赖trigger(target,property):找到update从映射关系中删除依赖于target.property的函数,并重新运行它们。可以有很多update函数依赖一个target.property,使用Set结构来存储:,可以存储在一个Map结构中:Map{property1:Set[cb1,cb2,cb3...],property2:Set[cb1,cb2,cb3...],property3:Set[cb1,cb2,cb3...],......}调用这个Map结构depsMap但是这只是同一个对象中的一个属性,如果有多个对象呢?所以需要再包裹一层,用另一个Map(称为targetMap结构)来包裹每个对象的Map,但是targetMap使用的是WeakMap结构,WeakMap的属性只能是对象:WeakMap{obj1:Map{property1:Set[cb1,cb2,cb3...],...},obj2:Map{property1:Set[cb1,cb2,cb3...],...},...}当数据读取后,track函数就是通过这个结构实现响应式属性和依赖它的更新函数的映射:consttargetMap=newWeakMap()functiontrack(target,property){if(!activeEffect)return//如果activeEffect为null,则返回//onlyactiveEffect只有在effect()运行时才有值letdepsMap=targetMap.get(target)//如果目标对象没有对应的depsMap,则新建一个if(!depsMap){targetMap.set(target,depsMap=newMap())}letdep=depsMap.get(property)//如果property没有对应的dep,则新建一个if(!dep){depsMap.set(财产,dep=newSet())}dep.add(activeEffect)//添加属性对应的效果进入映射结构}设置数据时,触发函数通过映射结构取出数据对应的所有更新函数,并executes:functiontrigger(target,property){constdepsMap=targetMap.get(target)if(!depsMap)returnconstdep=depsMap.get(property)if(!dep)return//dep是一个带有forEach的Set结构方法dep.forEach((效果)=>{effect()})}6.集成&使用集成以上代码://reactive.js//effect函数的实现letactiveEffect=nullfunctioneffect(cb){activeEffect=cbcb()activeEffect=null}//创建aresponseformulavariablefunctionfunctionreactive(obj){consthandler={get(target,propKey,receiver){constval=Reflect.get(...arguments)//读取数据track(target,propKey)//依赖集合if(typeofval==='object'){returnreactive(val)}returnval},set(target,propKey,newVal,receiver){constsuccess=Reflect.set(...arguments)//设置数据,returntrue/falsetrigger(target,propKey)//触发更新返回成功}}constproxyObj=newProxy(obj,handler)returnproxyObj}//依赖收集函数consttargetMap=newWeakMap()//存储映射的结构函数relationshiptrack(target,property){if(!activeEffect)returnletdepsMap=targetMap.get(target)//如果目标对象没有对应的depsMap,则新建一个if(!depsMap){targetMap.set(target,depsMap=newMap())}letdep=depsMap.get(property)//如果property没有对应的dep,则新建一个if(!dep){depsMap.set(property,dep=newSet())}dep.add(activeEffect)//将属性对应的effect加入映射结构}//分发更新函数functiontrigger(target,property){constdepsMap=targetMap.get(target)if(!depsMap)returnconstdep=depsMap.get(property)if(!dep)returndep.forEach((effect)=>{effect()})}测试使用:让obj=reactive({num1:10,num2:20,son:{num3:20},})letsum=0effect(()=>{sum=obj.num1+obj.num2+obj.son.num3})控制台。log(sum)//50obj.num1=100console.log(sum)//130可以看出sum是响应式参考:Vue3响应式原理及实现Vue3响应式原理+手写reactVue3响应式原理及reactive,effect,computed实现ES6Reflect和Proxy
