vue源码解读--codegen

目录导航
本节代码如下
vue源码解读--codegen
文章图片


辅助函数映射表
vue源码解读--codegen
文章图片
经过上一节分析我们知道,vue对ast树进行了一次标记,标记的结果就是br和div被标记为staticRoot。这一节,我们尝试分析ast=>code的过程。执行generate
vue源码解读--codegen
文章图片
实例化CodegenState,并将配置对象options缓存到私有属性option上,state则保存当前实例
若ast树不存在则返回一个"_c('div')",_c其实就是createElement函数。其实这里我们可以预想下code的形式,它和我们手写render是一样的:{tag,options,children}。显然当前我们的ast是存在的,故调用genElement传入ast和state
vue源码解读--codegen
文章图片
框红的位置,拿到的是我们的根ast节点即main,它的parent不存在,故跳过。对于其子节点ul、br、div而言则进入逻辑。由于v-pre有保留代码串的特性,故对于这样的标签及子标签是不需要去解析生成code的
对于我们当前的示例,会先走向框蓝的位置。此时的ast节点是ul。el.ifProcessed则会在当前次处理后被标记为true,有效避免递归死循环。调用genIf
vue源码解读--codegen
文章图片
【vue源码解读--codegen】将el.ifProcessed置为true,由于genElement是一次递归的过程,这将避免程序无出口造成 死循环
调用genIfConditions,在创建ast阶段,如果存在if指令,则会将其进行解析,并向数组push一位成员,该成员由当前的ast节点和对应的值两个key组成。故入参为:[{block:ul节点,exp:isList},{block:p,exp:undefined}]、undefined、undefined
vue源码解读--codegen
文章图片
如果conditions中无值,则调用_e()创建一个文本节点。我们这里有,则跳过。
使用shift拿到第一个成员,即{block:ul,exp:isList},进入if判断。并生成一段字符串返回:`isList?genElement函数返回值:genIfConditions调用`,经过下一步则实际为`isList?genElement函数返回值:genElement函数返回值`
--genIfConditions的入参为[{block:p,exp:undefined}\,在执行一二步,conditions跳过,拿到{block:p,exp:undefined},由于exp为undefined,故本次走else逻辑,返回字符串:`genElement函数`
(故,这里会重复执行两次genElement函数,一次是ul,一次是p)
ul

由于已经将el.ifProcessed标记为true,故本次将走向elde逻辑
vue源码解读--codegen
文章图片
显然ul下有子节点,故执行genChildren
vue源码解读--codegen
文章图片
`拿到li节点,进入判断,由于li上有v-for,故进入到框红的位置
由于li不是一个组件,故normalizationType=",0"
此时生成的字符串为:`genElement返回值,0`,再次进入genElement函数,当解析完li后则实际为`_c("li",{key:"i"},_v(_s(v))),0`
li——_c("li",{key:"i"},_v(_s(v)))
由于li标签上存在v-for,故本次将调用genFor
vue源码解读--codegen
文章图片
对于v-for指令而言,vue会要求我们提供key值,如果不存在则warn用户
返回一个字符串:`_l("list",function(v,i){return genElement返回值})`,再次调用genElement
,由于已经将el.forProcessed置为true,故将走向else逻辑,调用gendata

vue源码解读--codegen
文章图片
gendata将对标签存在的属性做一些处理,当前我们的li标签只有一个key还未被处理,故简化后的gendata如下
vue源码解读--codegen
文章图片
返回`{key:i}`
返回到genElement函数,data值为`{key:i}`。向下调用genchildren处理li内的插值,将返回`_v(_s(v)),_v(_s(v)),0`
故code为`_c("li",{key:"i"},_v(_s(v)))`
li标签内的插值——`_v(_s(v)),_v(_s(v)),0`
本次不满足for指令,故走else
vue源码解读--codegen
文章图片
--调用getNormalizationType将children拍平,我们这里不需要,故为0
--调用genNode,由于当前是一个表达式节点,type=2,故实际上调用的是genText
vue源码解读--codegen
文章图片
返回`_v(_s(v))`,故真正返回的字符串是'_v(_s(v)),_v(_s(v)),0'


一步一步走太过于繁琐,但是经过以上对ul和li以及li的子元素的分析,我们已经可以总结。codegen的过程是深度遍历的过程,它将对我们不同的ast节点类型调用不同的预先定义好的函数,每一个标签都对应_c函数,当标签存在不同的属性时它将会把他们解析成对象形式并作为_c的第二个参数。由此由内向外不断嵌套。并最终利用with进行包裹使用new Function将字符串转换成可执行的代码。其最终的基本像是将和我们手写render函数是一样的,如
createElement('main',{
staticClass:"app"
},[
createElement('ul',{},[createElement('li')]),
createElement('br'),
createElement('div')
])
只不过,vue将createElement替换成了_c,将ul、br、div合成了_l

    推荐阅读