Vue3源码分析Vue3出来有一段时间了。今天正式开始记录vue3.0.0-beta源码的学习心得。本文写于2020-06-10,脚手架使用vite-app0.20.0版本,内置vue3.0.0-beta.14。ps:可能大部分人不知道vue3的开发api。在讲解源码之前,先说说环境的使用方法。搭建vue3最简单的方法是使用作者的vite通过npm安装$npminitvite-app$cd$npminstall$npmrundev也可以通过yarn安装$yarncreatevite-app$cd$yarn$yarndev安装过程中可能会遇到以下问题(反正这道菜遇到过)Exception1:Novalidexportsmainfoundfor'C:\xxx\xxx\node_modules\@rollup\pluginutils的异常2:引擎“节点”与该模块不兼容。预期版本“>=10.16.0”。得到了》10.15.3异常1:本菜看了vite的issue,然后google+baidu一无所获,最后发现本菜的node版本是13.5.0(版本太高),异常2:很明显,node版本太低,最终解决方案是:本菜通过nvm将node版本切换到12.12.0,至于没用过nvm的童鞋可以试试,vite原理分析特别好用当浏览器识别到type="module"导入的是js文件时,内部导入会发起网络请求尝试获取该文件,然后就可以拦截路由/和.js结尾的请求了。然后使用node加载对应的.js文件constfs=require('fs')constpath=require('path')constKoa=require('koa')constapp=newKoa()app.use(asyncctx=>{const{request:{url}}=ctx//主页if(url=='/'){nctx.type="text/html"ctx.body=fs.readFileSync('./index.html','utf-8')}elseif(url.endsWith('.js')){//js文件constp=path.resolve(__dirname,url.slice(1))ctx.type='application/javascript'constcontent=fs.readFileSync(p,'utf-8')ctx.body=content}})app.listen(3001,()=>{console.log('监听我的密码,3001端口,开始~~')})如果只是简单的代码,这样加载就可以了。完全按需加载,当然会比webpack的语法解析性能快很多。但是,如果在第三方库中遇到上面的代码,就会找不到.js文件所在的位置。这时候vite会使用es-module-lexer将文件解析成ast,得到import的地址。通过分析导入的内容,识别是否是第三方库(这个主要看前面是不是相对路径)。如果是第三方库,去node_modules里面找。在vite中,给第三方库添加前缀/@modules/,然后发现/@modules/后,进入第三方库逻辑if(url.startsWith('/@modules/')){//这是node_module中的东西constprefix=path.resolve(__dirname,'node_modules',url.replace('/@modules/',''))constmodule=require(prefix+'/package.json').moduleconstp=path.resolve(prefix,module)constret=fs.readFileSync(p,'utf-8')ctx.type='application/javascript'ctx.body=rewriteImport(ret)}这样,第三-派对库也可以被解析。然后是.vue单文件解析。首先xx.vue返回的格式大概是这样的const__script={setup(){...}}import{renderas__render}from"/src/App.vue?type=template&t=1592389791757"__script.render=__renderexportdefault__script然后可以使用@vue/compiler-dom将html解析成render解析。css更简单。只需传递document.createElement('style')然后注入即可。参考——大圣的知乎文章ps:具体源码我还没看(先做点Vue3吧)。Reactive正式进入正题。作为vue2的用户,最想知道的就是vue3的数据劫持和双向绑定。在vue3中,双向绑定和可选,如果需要使用双向绑定,需要使用reactive方式进行数据劫持。在此之前,你需要知道一个函数setupsetup是CompositionAPI的入口点。setup可以返回一个对象,对象的属性会合并到渲染上下文中,可以直接在模板中使用setup或者返回render函数。现在开始写一个简单的vueemmm。这样点击按钮就可以动态改变dom中的计数值。现在开始解读响应式源代码。首先找到reactivity.esm-browser.js文件,找到第626行。functionreactive(target){//如果试图观察只读代理,返回只读版本。如果(目标&&target.__v_isReadonly){返回目标;}returncreateReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers);}上面_v_isReadonly其实是一个typescript枚举值,reactive='__v_reactive',readonly='__v_readonly'}不同的枚举值对应不同的数据劫持方式,比如reactive,shallowReactive,readonly,shallowReadonly然后在649行输入createReactiveObject,意思是:创建响应式对象函数createReactiveObject(target,isReadonly,baseHandlers,collectionHandlers){//略...//如果target已经被代理,则返回targetif(target.__v_raw&&!(isReadonly&&target.__v_isReactive)){returntarget;}//target已经有对应的Proxyif(hasOwn(target,isReadonly?"__v_readonly"/*readonly*/:"__v_reactive"/*反应式*/)){returnisReadonly?target.__v_readonly:target.__v_reactive;}if(!canObserve(target)){返回目标;}//重要...//collectionHandlers:References类型的劫持,//baseHandlers:基本类型的劫持constobserved=newProxy(target,collectionTypes.has(target.constructor)?collectionHandlers:baseHandlers);def(target,isReadonly?"__v_readonly"/*readonly*/:"__v_reactive"/*reactive*/,observed);returnobserved;}createReactiveObject为了防止重复劫持做了以下几件事Read-onlyhijacking根据不同的类型选择不同的劫持方法(collectionHandlers或baseHandlers)劫持的主要方法是通过Proxy方法,(使用Proxy可以看阮老师的blog),找到定义mutableHandlers的地方在第338行constmutableHandlers={get,set,deleteProperty,has,ownKeys};//第229行constget=/*#__PURE__*/createGetter();//line251functioncreateGetter(isReadonly=false,shallow=false){returnfunctionget(target,key,receiver){//__v_isReactive,__v_isReadonly,__v_raw的一些处理//略...//数组操作consttargetIsArray=isArray(目标);如果(targetIsArray&&hasOwn(arrayInstrumentations,key)){returnReflect.get(arrayInstrumentations,key,receiver);}//非数组constres=Reflect.get(target,key,receiver);//其他调用track返回res的情况//省略...//如果可写,则调用track!isReadonly&&track(target,"get"/*GET*/,key);//如果它是一个对象。然后递归返回isObject(res)?是只读的吗?//这里需要延迟访问readonly和reactive以避免循环依赖readonly(res):reactive(res):res;};}mutableHandlers主要是一个Proxy,各种方法常量。get指向方法createGetter,creatingget劫持createGetter主要做了以下事情异常处理如果是数组,hasOwn(arrayInstrumentations,key)然后调用arrayInstrumentations获取值调用track对象迭代reactive那么arrayInstrumentations是什么大批?我们来到源代码的第234行。constarrayInstrumentations={};['includes','indexOf','lastIndexOf'].forEach(key=>{arrayInstrumentations[key]=function(...args){//constarr=toRaw(this);for(leti=0,l=this.length;i{addRunners(effects,computedRunners,dep);});}//稍微...construn=(effect)=>{scheduleRun(e效果、目标、类型、键、额外信息);};computedRunners.forEach(运行);effects.forEach(运行);}trigger最后用到了set函数中,源码3855行,这个set就是用于数据劫持的set函数set(target,key,value,receiver){value=toRaw(value);constoldValue=target[key];如果(isRef(oldValue)&&!isRef(value)){oldValue.value=value;返回真;}consthadKey=hasOwn(target,key);constresult=Reflect.set(target,key,value,receiver);{constextraInfo={oldValue,newValue:value};if(!hadKey){trigger(target,"add"/*ADD*/,key,extraInfo);}elseif(hasChanged(value,oldValue)){trigger(target,"set"/*SET*/,key,extraInfo);}}}returnresult;}在源码的第3900行,用在mutableHandlers和readonlyHandlers等函数中,记得吗?什么是mutableHandlers?大家可以回到文章开头Reactive源码讲解开头的createReactiveObject方法。通过Proxy劫持数据时,使用mutableHandlersreactive。所以,这是一个循环。其实effect是响应式的核心,在mountComponent、doWatch、reactive中都有调用。因为劫持是通过Proxy在reactive中实现的。当Proxy劫持set时调用触发器。然后清除targger中的集合并触发目标上的所有效果最终触发patchgameover。最后,觉得有用请点个赞。回复fullstack或者vue有礼物