前言
transform阶段是对AST树的升华,可以让其在generate阶段更好的产生的渲染函数,这里我们只关心内置的transform,用户的transfomr不去看,内置的transform如下:
Vue的提供了十四个transform,但是在浏览器平台上最多只会执行十三个,常态下只会执行十二个,有一个是兼容模式下才会执行。接下来就是看看这十三个的处理逻辑。
分析v-if/v-else-if/v-else指令
分析这三个指令主要有三个流程,第一步是创建条件分支,首先是v-if,除了创建分支以外,还会创建一个ifNode,这被称为v-if根,而v-if的条件分支会自动的放入这个根中。
而v-else-if和v-else在创建分支之前,它会从自己的位置往上去找离自己最近的v-if根,因为这两个指令必须在v-if之后使用,如果没有找到就会报错,在这过程中,如果遇到字符串节点并且去掉空格之后长度是0的会将其移除。
在找到之后需要做一个检验,因为v-else可以在v-else-if之后也可以在v-if之后,但是v-else-if必须要在v-if,所以如果v-if根中的条件分支的最后一个不存在条件表达式(v-else不需要条件表达式),说明是v-if后面跟着v-else了,不能再跟着v-else-if,会直接报错。
前面的校验通过之后,会将v-else-if或v-else在AST树中的node移动到v-if根中,其实是先移除自己在AST树中的节点,再创建条件分支,在添加进v-if根中之前会判断分支的key是否重复,重复报错。
第二个流程是去执行processCodegen,这倒是非常简单,就是给多个相同等级添加不同的key,而后返回一个退出函数。
第三个流程就是去执行processCodegen返回的退出函数,去创建属于分支的codegen,退出函数的执行时机是要处理完分支里面的子元素。
v-if的codegen是正常的在v-if根上,而v-else-if或v-else则是放在v-if的codegen上,如果三个都存在,那么存放的关系是:v-else放在v-else-if上,v-else-if放在v-if上。所以v-if是直接去创建codegen,其他两个在创建codegen的时候,需要去拿到自己前面的codegen。
进入createCodegenNodeForBranch中,v-if和v-else-if带条件走if,而v-else走else。前面也说到了,三个指令的codegen节点的存放关系,所以v-if和v-else-if需要产生一个条件表达式对象,v-else不需要。v-if/v-else-if/v-else指令分析完毕。
分析v-for指令
v-for的语义分析和v-if的流程差不多,主要是靠transformFor,里面是去调用processFor,我们先去看它的实现。
确认v-for指令的表达式存在,执行parseForExpression去解析它。
首先我们要知道几个正则表达式,假设我们的表达式长这样:(item, name, index) in|of list,
forAliasRE会匹配出三部分,分别是:(item, name, index)、in|of、list
stripParensRE匹配是 (item, name, index)中的括号,得出里面的内容item, name, index
forIteratorRE 匹配item, name, index中的 ,name和,index
进入parseForExpression,会先用forAliasRE匹配出前半部和后半部,后半部也就是list,会产生一个srouce对象。放到result中(这是表达式解析结果的汇总)
这里是要拿到(item, name, index)中name和index,这两个东西是可选参数,这里匹配是否存在,存在才会进入它们两个的处理流程。匹配结果interatorMatch[1]是name, interatorMatch[2]是index。
处理name,获取name在表达式中的位置,通过createAliasExpression创建别名表达式对象。放到result中。
处理index和item,和处理name一样,这里不再说明。 都是放入result中,最后把result返回回去。
回到processFor中,通过parseForExpression返回的result创建forNode。和ifNode一样,会替换AST树中的自身。之后去执行processCodegen。
函数开始,拿到和创建一系列东西,为后面的流程和退出函数做准备。
创建forNode的codegen,返回退出函数,在processFor中也返回一退出函数,在后面合适的时机执行。
这里我们直接跳到执行退出函数的时候,processFor的退出函数中会scopes减一,表示一个vFor处理完成。去执行prcessCodegen的退出函数。
childrenBlock是后面用来保存codegen的,slotOutlet是用来存储找到的插槽出口,没有就是null,needFragmentWapper和vIf中的一样。
处理的流程主要分为两大类,插槽出口和普通元素,如果是插槽出口,需要判断v-for是不是在<slot />上,如果在<slot />上,只需要注入key即可。如果是<template v-for><slot /></template,那么就会是有多个文本或者元素,需要为每一个children产生一个Fragment。
如果是普通元素,那就可以直接使用children的codegen,但是需要把它们标记为块。
最后因为v-for是使用renderList渲染,需要处理一下参数,但可能会带有v-memo指令,也需要做额外的处理。如果带有v-memo指令,则需要加入一些复合表达式对象。比如(renderList)函数参数、缓存校验等。
没有带有v-memo指令,那就只需要加入renderList形参对象即可。v-for分析完毕。
分析v-memo
既然前面提到了v-memo,那就接着去看v-memo的语义分析。v-memo的是靠transformMemo,处理非常简单,验证指令存在且缓存中不存在当前节点,就会添加缓存,剩下的处理在退出函数中。
退出函数主要做的有两个工作,一是把非组件的子树变为block,而后就是在codegen外包一层函数withMemo,v-memo处理完成。
分析expression
表达式一共分为两种,一种是插值表达式:{{exp}},还有一种就是元素上的指令表达式,比如:v-bind:arg="arg",这些都交给transformExpression。
首先是插值,它会去执行processExpression,其实这个函数也没啥好看的,那么一大段代码,居然都是非浏览器环境下执行,在浏览器环境下执行只有一点一点。
进行简单的表达式验证,并且返回。
处理指令表达式时,不要处理v-on和v-for,它们会经过特殊处理,v-on包装成内联语句。而v-for会变成renderList渲染。也是去执行processExpression进行处理。如果arg存在并且不是静态的也需要处理。表达式处理完成。
结尾
本篇文章就先说v-for、v-if、v-memo以及expression的语义分析,其他的在后面再说。敬请期待。