当前位置: 首页 > Web前端 > vue.js

Vue3模板编译优化解读

时间:2023-03-31 22:15:30 vue.js

今天这篇文章打算学习Vue3和Vue2下模板编译的区别,以及VDOM下Diff算法的优化。在编译入口了解过Vue3的同学一定知道,Vue3引入了一个新的组合API。组件挂载阶段会调用setup方法,然后判断render方法是否存在。如果没有,将调用编译方法将模板转换为渲染。//packages/runtime-core/src/renderer.tsconstmountComponent=(initialVNode,container)=>{constinstance=(initialVNode.component=createComponentInstance(//...params))//调用设置setupComponent(instance)}//packages/runtime-core/src/component.tsletcompileexportfunctionregisterRuntimeCompiler(_compile){compile=_compile}exportfunctionsetupComponent(instance){constComponent=instance.typeconst{setup}=Componentif(setup){//...callsetup}if(compile&&Component.template&&!Component.render){//如果没有render方法//调用compile将template转换为render方法Component.render=compile(Component.template,{...})}}这部分是runtime-core中的代码。上一篇文章提到Vue分为完整版和运行时版。如果使用vue-loader处理.vue文件,一般会直接将.vue文件中的模板处理到render方法中。//需要编译器Vue.createApp({template:'

{{hi}}
'})//不需要Vue.createApp({render(){returnVue.h('div',{},this.hi)}})完整版和runtime版本的区别在于完整版会引入compile方法。如果是vue-cli生成的项目,这部分代码会被抹去,编译过程会放在打包阶段,优化性能。runtime-dom中提供了registerRuntimeCompiler方法用于注入compile方法。【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-sj0Ctvim-1665390661650)(https://p3-juejin.byteimg.com...)]主要流程在完整版index.js中,调用了registerRuntimeCompiler注入编译。接下来我们看看注入的compile方法主要做了什么。//packages/vue/src/index.tsimport{compile}from'@vue/compiler-dom'//编译缓存constcompileCache=Object.create(null)//注入编译方法functioncompileToFunction(//templatetemplate:string|HTMLElement,//Compilationconfigurationoptions?:CompilerOptions):RenderFunction{if(!isString(template)){//如果模板不是字符串//它被认为是DOM节点,获取innerHTMLif(template.nodeType){template=template.innerHTML}else{returnNOOP}}//如果缓存中存在,直接从缓存中获取constkey=templateconstcached=compileCache[key]if(cached){returncached}//如果存在是一个ID选择器,获取DOM元素后,取innerHTMLif(template[0]==='#'){constel=document.querySelector(template)template=el?el.innerHTML:''}//调用编译获取渲染代码const{code}=compile(template,options)//将渲染代码转换为函数constrender=newFunction(code)();//返回render方法时放入缓存return(compileCache[key]=render)}//注入compileregisterRuntimeCompiler(compileToFunction)在讲Vue2模板编译的时候已经提到了。编译方法主要分为三个步骤。Vue3的逻辑类似:模板编译,将模板代码转化为AST;优化AST以方便后续的虚拟DOM更新;生成代码,将AST转换为可执行代码;参考vue实战视频讲解:进入学习//packages/compiler-dom/src/index.tsimport{baseCompile,baseParse}from'@vue/compiler-core'exportfunctioncompile(template,options){returnbaseCompile(template,options)}//packages/compiler-core/src/compile.tsimport{baseParse}from'./parse'import{transform}from'./transform'import{transformIf}from'./transforms/vIf'import{transformFor}from'./transforms/vFor'import{transformText}from'./transforms/transformText'import{transformElement}from'./transforms/transformElement'import{transformOn}from'./transforms/vOn'import{transformBind}from'./transforms/vBind'import{transformModel}from'./transforms/vModel'exportfunctionbaseCompile(template,options){//解析html并转换为astconstast=baseParse(template,options)//优化ast,标记静态节点transform(ast,{...options,nodeTransforms:[transformIf,transformFor,transformText,transformElement,//...省略了一些转换],directiveTransforms:{on:transformOn,bind:transformBind,model:transformModel}})//将ast转换为可执行代码returngenerate(ast,options)}计算PatchFlag这里的大体逻辑和前面的没有太大区别。主要原因是optimize方法变成了transform方法,一些模板语法默认会进行transform。这些变换就是后续的虚拟DOM优化。关键,我们先看看transform的代码。//packages/compiler-core/src/transform.tsexportfunctiontransform(root,options){constcontext=createTransformContext(root,options)traverseNode(root,context)}exportfunctiontraverseNode(node,context){context.currentNode=nodeconst{nodeTransforms}=contextconstexitFns=[]for(leti=0;i{//transformElement不执行任何逻辑,而是直接返回一个退出函数//indi满足transformElement需要等待所有子节点返回函数postTransformElement(){const{tag,props}=nodeletvnodePropsletvnodePatchFlagconstvnodeTag=node.tagType===ElementTypes.COMPONENT?resolveComponentType(node,context):`"${tag}"`letpatchFlag=0//检测节点属性if(props.length>0){//检测节点属性的动态部分constpropsBuildResult=buildProps(node,context)vnodeProps=propsBuildResult.propspatchFlag=propsBuildResult.patchFlag}//检测子节点if(node.children.length>0){if(node.children.length===1){constchild=node.children[0]//检查子节点是否为动态文本if(!getStaticType(child)){patchFlag|=PatchFlags.TEXT}}}//格式化patchFlagif(patchFlag!==0){vnodePatchFlag=String(patchFlag)}node.codegenNode=createVNodeCall(context,vnodeTag,vnodeProps,vnodeChildren,vnodePatchFlag)}}buildProps将执行节点属性的遍历。由于内部源码涉及到很多其他细节,这里简化代码,只保留patchFlag相关的逻辑。exportfunctionbuildProps(node:ElementNode,context:TransformContext,props:ElementNode['props']=node.props){让patchFlag=0for(leti=0;iname:{{name}}

该节点包含一个可变类属性(CLASS=1<<1)
该节点包含一个可变样式属性(STYLE=1<<2)
可以看到PatchFlags都是通过左移运算符由数字1计算出来的。exportconstenumPatchFlags{TEXT=1,//1,二进制00000001CLASS=1<<1,//2,二进制00000010STYLE=1<<2,//4,二进制00000100PROPS=1<<3,//8,binary00001000...}从上面的代码可以看出,patchFlag的初始值为0,|(or)每次都对patchFlag进行操作。如果当前节点是一个只有动态文本的子节点,也有动态样式属性,则最终的patchFlag为5(二进制:00000101)。name:{{name}}

patchFlag=0patchFlag|=PatchFlags.STYLEpatchFlag|=PatchFlags.TEXT//或操作:只有两个对应的二进制位一个是1,结果对应的位为1。//00000001//00000100//------------//00000101=>十进制5【外链图片传输失败,源站可能有防盗链机制,是建议把图片保存下来直接上传(img-MDpZgiyc-1665390661653)(https://p3-juejin.byteimg.com...)]我们把上面的代码放到Vue3中运行:constapp=Vue.createApp({data(){return{color:'red',name:'shenfq'}},template:`
name:{{name}}

`})app.mount('#app')最后生成render方法如下,和我们之前的描述基本一致。【外链图片传输失败,源站可能有防盗链接机制,建议保存图片直接上传(img-z6O1fmVz-1665390661655)(https://p3-juejin.byteimg.com...)]render优化Vue3在虚拟DOMDiff中,会取出patchFlag和需要的diff类型进行&(AND)运算,如果结果为真,则进入对应的diff。【外链图片传输失败,源站可能有防盗链接机制,建议保存图片直接上传(img-6qlLYmPe-1665390661657)(https://p3-juejin.byteimg.com...)]取之前示例模板:name:{{name}}

如果此时修改了name,则p节点进入diff阶段,此时会判断patchFlag&PatchFlags.TEXT,此时结果为true,说明p节点有文本修改。【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-Tzwu7A4S-1665390661659)(https://p3-juejin.byteimg.com...)]patchFlag=5patchFlag&PatchFlags.TEXT//或运算:只有对应的两个二进制位都为1,结果位才为1。//00000101//00000001//------------//00000001=>decimal1if(patchFlag&PatchFlags.TEXT){if(oldNode.children!==newNode.children){//修改文本hostSetElementText(el,newNode.children)}}但是,patchFlag&PatchFlags.CLASS判断时,由于节点没有动态Class,返回值为0,所以不会diff出节点的class属性。使用它来优化性能。【外链图片传输失败,源站可能有防盗链机制,建议保存图片直接上传(img-O6SMFmwd-1665390661661)(https://p3-juejin.byteimg.com...)]patchFlag=5patchFlag&PatchFlags.CLASS//或运算:只有对应的两个二进制位都为1,结果位才为1。//00000101//00000010//------------//00000000=>decimal0总结其实Vue3相关的性能优化还有很多,这里只讲patchFlag的十分之一,给大家说说Vue3的内容。在Vue3正式发布之前,看到Diff流程会通过patchFlag进行优化,所以打算看看它的优化逻辑。总的来说,自己还是有所收获的。

最新推荐
猜你喜欢