快捷搜索:

Vue 中 template 有且只能一个 root的原因解析(源码分

Vue中template有且只能一个root的原因解析(源码分析)

  并没有影响到各种面经的正常出现,可谓是络绎不绝(学不动...)。然后,在前段时间也看到一个这样的关于

  可能,大家在平常开发中,用的较多就是template写html的形式。当然,不排除用JSX和render()函数的。但是,究其本质,它们最终都会转化成render()函数。然后,再由render()函数转为Vritual DOM(以下统称VNode)。而render()函数转为VNode的过程,是由createElement()函数完成的。

  因此,本次文章将会先讲述Vue为什么限制template有且只能一个root。然后,再分析Vue如何规避出现多root的情况。那么,接下来我们就从源码的角度去深究一下这个过程!

  这里,我们会分两个方面讲解,一方面是createElement()的执行过程和定义,另一方面是VNode的定义。

  createElement()函数在源码中,被设计为render()函数的参数。所以 官方文档 也讲解了,如何使用render()函数的方式创建组件。

  可以很简单地看出,源码中通过call()将当前实例作为context上下文以及$createElement作为参数传入。

  Vue2x 源码中用了大量的 call 和 apply,例如经典的 $set() API 实现数组变化的响应式处理就用的很是精妙,大家有兴趣可以看看。

  现在,见到了我们平常使用的createElement()的庐山真面目。这里,我们并不看函数内部的执行逻辑,这里分析一下这五个参数:

  可以看出,createElement()的设计,是针对一个节点,然后带children的组件的VNode的创建。并且,它并没有留给你进行多root的创建的机会,只能传一个根root的tag,其他都是它的选项。

  我想大家都知道Vue2x用的静态类型检测的方式是flow,所以它会借助flow实现自定义类型。而VNode就是其中一种。那么,我们看看VNode类型定义:

  前面,我们分析了createElement()的调用时机,知道它最终返回的就是 VNode。那么,现在我们来看看VNode的定义:

  显而易见的是VNode的设计也是一个root,然后由children不断延申下去。这样和前面createElement()的设计相呼应,不可能会出现多root的情况。

  可以看到VNode和createElement()的设计ASP编程,就只是针对单个root的情况进行处理,最终形成树的结构。那么,我想这个时候可能有人会问为什么它们被设计树的结构?。

  而针对这个问题,有两个方面,一方面是树形结构的VNode转为真实DOM后,我们只需要将根VNode的真实DOM挂载到页面中。另一方面是DOM本身就是树形结构,所以VNode也被设计为树形结构,而且之后我们分析template编译阶段会提到AST抽象语法树,它也是树形结构。所以,统一的结构可以实现很方便的类型转化,即从AST到Render函数,从Render函数到VNode,最后从VNode到真实DOM。

  并且,可以想一个情景,如果多个root,那么当你将VNode转为真实DOM时,挂载到页面中,是不是要遍历这个DOM Collection,然后挂载上去,而这个阶段又是操作DOM的阶段。大家都知道的一个东西就是操作DOM是非常昂贵的。所以,一个root的好处在这个时候就体现出它的好处了。

  其实这个过程,让我想起红宝书中在讲文档碎片的时候,提倡把要创建的 DOM 先添加到文档碎片中,然后将文档碎片添加到页面中。(PS:想想第一次看红宝书是去年 4 月份,刚开始学前端,不经意间过了快一年了....)

  在文章的开始,我们也说了在Vue中无论是写template还是render,它最终会转成render()函数。而平常开发中,我们用template的方式会较多。所以,这个过程就需要Vue来编译template。

  而对于开发中,如果你写了多个root的组件,在parse的时候,即生成AST抽象语法树的时候,Vue就会过滤掉多余的root,只认第一个root。

  而parse的整个过程,其实就是正则匹配的过程,并且这个过程会用栈来存储起始标签。整个parse过程的流程图:

  然后,我们通过一个例子来分析一下,其中针对多root的处理。假设此时我们定义了这样的template:

  显然,它是多root的。而在处理第一个<div>时,会创建对应的ASTElement,它的结构会是这样:

  然后,继续遍历。对于<span>也会创建一个ASTElement并入栈,然后删除继续下一次。接下来,会匹配到</span>,此时会处理标签的结束,例如于栈顶ASTElement的tag进行匹配,然后出栈。接下来,匹配到</div>,进行和span同样的操作。

  最后,对于第二个root的<div>,会做和上面一样的操作。但是,在处理</div>时,此时会进入判断multiple root的逻辑,即此时字符串已经处理完了,但是这个结束标签对应的ASTElement并不等于我们最初定义的root。所以此时就会报错:

  而且,该ASTElement也不会加入最终的AST中,所以之后也不可能会出现多个root的情况。

  可以看出,template编译的最终的目标就是构建一个AST抽象语法树。所以,它会在创建第一个ASTElement的时候就确定AST的root,从而确保root唯一性。

  不了解Vue初始化过程的同学,可能不太清楚_render过程。你可以理解为渲染的过程。在这个阶段会调用render方法生成VNode,以及对VNode进行一些处理,最终返回一个VNode。

  前面在讲createElement的时候,也讲到了render()需要返回VNode。所以,这里是防止部分骚操作,return了包含多个VNode的数组。

  通过阅读,我想大家也明白了为什么 Vue 中 template 有且只能一个 root ?。Vue这样设计的出发点可能很简单,为了减少挂载时DOM的操作。但是,它是如何处理多root的情况,以及相关的VNode、AST、createElement()等等关键点,个人认为都是很值得深入了解的。怎么用asp源码在电脑上制作视频文件格式不对怎么用asp源码在电脑上制作视频文件格式不变

您可能还会对下面的文章感兴趣: