大家好,我是墨扣,最近一直在做vue3相关的内容,比如源码分析,mini-vue3开发等。回顾前面几章的内容,下面的内容主要是在前面几章中讲述的。新构建工具vite原理及vue3从头实现使用新姿势新api:reactive使用及源码分析Trackingcollectiontrack实现及源码分析Trackingtrigger实现及源码分析Responsivecoreeffectandtrack,triggerworking原理和源码解析好了,本章目标:从零开始完成一个Vue3!必须知道的前提知识是效果器、轨道、触发器的工作原理。详情请参考公众号->前端进阶教程,有温度无广告的前端技术公众号。这里简单分析一下这三个函数的作用。track:收集依赖,存放到targetMatrigger:触发依赖,使用targetMapeffect:处理副作用,请看本章源码uuz急需star谋生。前两章连载内容:Vue3.x深入浅出系列(连载一)Vue3.x深入浅出系列(连载二)Vue3先上手实现。我们的两个全局变量是用来存储和定位跟踪依赖的,也就是track和trigger使用的仓库。让targetMap=newWeakMap();让activeEffect;那么第一个需要设计的方法就是track,还记得vue3中这个track是怎么调用的吗?跟踪(obj,“得到”,“x”);track会查找obj.x是否被跟踪,如果没有找到,将obj.x放入targetMap中(完成跟踪任务),将obj.x作为map的key,activeEffect作为map的value。抛开值异常处理之类的,track只做了一件事,将activeEffect插入到targetMap中;functiontrack(target,key){//首先判断obj是否被trackedletdepsMap=targetMap.get(target);if(!depsMap){//如果没有被追踪,添加一个targetMap.set(target,(depsMap=newMap()));}//然后寻找要跟踪的obj.xletdep=depsMap.get(key);if(!dep){//如果没有被跟踪,添加一个depsMap.set(key,(dep=newSet()));}//如果不添加activeEffect然后添加if(!dep.has(activeEffect)){dep.add(activeEffect);}}然后写一个trigger,还记得vue中trigger是怎么调用的吗?trigger(obj,'set','x')trigger只会去targetMap中寻找obj.x的跟踪任务,如果找到,去掉重复的,然后执行任务。也就是说:除了值相关的异常,触发器只做一件事:从targetMap中获取值,然后调用函数取值。functiontrigger(target,key){//寻找跟踪项constdepsMap=targetMap.get(target);//如果没有找到什么都不做if(!depsMap)return;//deduplicationconsteffects=newSet()depsMap.get(key).forEach(e=>effects.add(e))//Executeeffects.forEach(e=>e())}最后是效果。还记得vue3里面打工仔的api是怎么调用的吗?effect(()=>{console.log('runcb')})effect收到一个回调函数,然后发送到track。所以我们可以做到这一点。Effect定义了一个内部函数_effect并执行它。返回一个闭包,内部的_effect也做了两件事,将自己赋值给activeEffect来执行effect回调函数。优秀代码呼之欲出。functioneffect(fn){//定义一个内部_effectconst_effect=function(...args){//在执行期间将自身分配给activeEffectactiveEffect=_effect;//执行回调returnfn(...args);};_影响();//返回闭包return_effect;}所有的前置项都完成了,现在开始完成一个reactive,这是一个基于对象的响应式api。还记得如何在vue3中使用reactive吗?setup(){constauthor=reactive({name:'mokou',})constappendName=()复制代码=>author.name+='优秀';return{author,appendName};}通过上面优秀的代码,很容易实现vue3的响应式操作。通过回顾前面章节的内容,我们知道reactive是通过Proxy代理数据来实现的。这样我们就可以通过Proxy调用track和trigger,劫持getter和setter完成响应式设计exportfunctionreactive(target){//代理数据returnnewProxy(target,{get(target,prop){//执行跟踪track(target,prop);returnReflect.get(target,prop);},set(target,prop,newVal){Reflect.set(target,prop,newVal);//触发效果trigger(target,prop);returntrue;}})}就是这样。一切准备就绪,让我们挂载我们的假vue3导出函数update(el,instance);}functionupdate(el,instance){el.innerHTML=instance.render()}用mini-vue3写一个demo来测试一下。参考vue3的写法。定义设置和渲染。constApp={$data:null,setup(){letcount=reactive({num:0})setInterval(()=>{count.num+=1;},1000);返回{计数};},render(){return``}}mount(App,document.body)执行吧,果然是优秀的代码。响应式执行是正常的,每次执行setInterval都会重写页面刷新count.num的数据。源码见uuz,ps:7月23日源码已经支持jsx,通过以上50+行代码,轻松实现了vue3的响应式。但这是结束了吗?还存在以下问题。Proxy必须传入objectrender函数和h函数并且是正确的(Vue3的h函数现在是2而不是之前的createElement)。别再说虚拟dom的递归了--!,我不听。ref使用reactive有个缺点,就是Proxy只能代理对象,不能代理基本类型。如果调用这段代码newProxy(0,{}),浏览器会反馈给你UncaughtTypeError:Cannotcreateproxywithanon-objectastargetorhandler所以,对于基本类型的代理。我们需要一种新的方式,在vue3中,基本类型的新api是refexportdefault{setup(){constcount=ref(0);返回{计数};}}实现ref其实很简单:用js对象自带的getter实现一个栗子:letv=0;letref={getvalue(){console.log('get')returnv;},设置值(val){console.log('set',val)v=val;}}参考值;//打印getref.value=3;//printsetthentrackandtrigger通过前面章节实现直接reffunctionref(target){letvalue=targetconstobj={getvalue(){track(obj,'value');返回值;},设置值(newVal){如果(newVal!==value){value=newVal;触发器(obj,“值”);}}}returnobj;}computed那么如何实现computed呢?首先:参考vue3的computed使用方法letsum=computed(()=>{returncount.num+num.value+'!'})盲猜可以得出一个思路,可以通过改造实现effect,也就是在effectrun方法还没有执行的那一刻调用。所以我们可以添加一个惰性参数。函数效果(fn,options={}){const_effect=function(...args){activeEffect=_effect;返回fn(...args);};//添加这段代码if(!options.lazy){_effect();}return_effect;}然后computed可以写内部执行effect(fn,{lazy:true})来保证computed执行时不会触发回调。通过对象的getter属性,在使用计算值时执行回调。使用dirty来防止内存溢出。好的代码来了:functioncomputed(fn){letdirty=true;让值;让_计算;construnner=effect(fn,{lazy:true});_computed={getvalue(){if(dirty){value=runner();脏=假;}返回值;}}return_computed;}所以问题是dirty在第一次执行后设置为false怎么重置呢?vue3此时的解决方案是在effect上加一个scheduler来处理sideeffects。函数效果(fn,options={}){const_effect=function(...args){activeEffect=_effect;返回fn(...args);};如果(!options.lazy){_effect();}//添加这一行_effect.options=options;return_effect;}现在您有了一个调度程序,您需要更改触发器以处理新的调度程序。函数触发器(目标,键){constdepsMap=targetMap.get(目标);如果(!depsMap)返回;consteffects=newSet()depsMap.get(key).forEach(e=>effects.add(e))//改变这一行effects.forEach(e=>scheduleRun(e))}//添加一个方法函数scheduleRun(effect){如果(effect.options.scheduler!==void0){effect.options.scheduler(effect);}else{效果();}}然后,结合上面的代码,computed就完成了functioncomputed(fn){letdirty=true;让值;让_计算;construnner=effect(fn,{lazy:true,scheduler:(e)=>{if(!dirty){dirty=true;trigger(_computed,'value');}}});_computed={getvalue(){if(dirty){value=runner();脏=假;}track(_computed,'value');返回值;}}return_computed;}总结reactive的核心是track+trigger+Proxyref是通过对象自身的getter和setter配合track+trigger实现的computed其实是在effect的基础上进行了改进。下一章:如何结合vue3和jsx?说到底,原创不易,还是用三联来安慰一下我哥吧。请查看uuz的源代码。本文内容来自https://github.com/zhongmeizhi/FED-note。回复fullstack或者vue有礼物
