「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。
一、前情回顾 & 背景
上一篇小作文讨论了 genElement 的第一个情况——genStatic 获取渲染结果的过程,genStatic 主要做了以下工作:
- 标记当前
el.staticProcessed为true,防止被重复处理; - 递归调用
genElement,产生各个节点的渲染函数; - 当
genElement最后的else就是处理普通元素的渲染的。首先调用genData获取元素上的data, 然后调用genChildren()将子元素都处理成由render函数代码组成的数组项,每一项都渲染一个子元素; genStatic获取genElement返回的结果,包裹with(this)语句,然后push到staticRenderFns;genStatic的最后用将当前静态根节点处理成_m 方法的运行时调用,_m(当前元素在 staticRenderFns 的索引, true 或 '')
本篇小作文将要讨论 Vue 处理 v-for 指令的 genFor 方法;从上面的 genStatic 的返回值 _m(index, true) 能看出,ast 节点都被转变成了运行时渲染帮助函数的调用,这些帮助函数就会创建真实的 DOM 节点;v-for 也不例外,最终也会被处理成 _l 方法的调用;
二、v-for 和 genFor 方法
前面在说 parse 阶段将模板转换成 ast 节点时讲过的, v-for 被处理成 el.for 和 el.alias 两个 el 上的属性,el.for 的值指向 可迭代对象,el.alias 指向 可迭代对象的条目名,比如下面例子中的 someArr 就是可迭代对象,item 就是可迭代对象条目名称:
<span v-for="item in someArr" :key="index">{{item}}</span>
genFor 方法用于在 ast 生成 render 函数的 generate 阶段,被 genElement 调用,生成处理 v-for 的渲染函数代码,即渲染函数 "_l(exp, funciton(alias, iterator1, iterator2) {return _c(tag, data, children)})";genFor 调用的前提条件时是 el.for 属性有值,且 el.forProcessed 不为 true;
export function genElement (): string {
if(....){
} else if (el.for && !el.forProcessed) {
// genFor 被调用,用于处理带有 v-for 的 ast 节点
return genFor(el, state)
}
return code
}
三、genFor 方法
方法位置:src/compiler/codegen/index.js -> function genFor
方法参数:
el,ast节点对象;state,CodegenState对象altGen/altHelper暂时忽略
方法作用:根据 el.for 和 el.alias 处理 v-for 指令得到运行时的渲染函数代码,形如:
"_l(exp, function (alias, iterator1, iterator2) { return _c(tag, data, chilrend)})";具体步骤如下:
- 获取可迭代对象即
el.for属性值赋值给exp常量; - 获取可迭代对象的条目名称 即
el.alias属性值赋值给alias; - 获取迭代器
el.iterator1/el.iterator2;迭代器是个啥?即v-for="(val, key) in obj",此时iterator1是key,iterator2是val; - 检测使用
v-for指令时节点是否相应的设置了key属性,如果未设置则抛出警告; - 标记当前
el.forProcessed为true,标识已经被处理过,防止递归genElement时重复处理导致死循环; - 和
genStatic一样,这里并不处理具体的生成render 函数的逻辑,genFor只处理标记el.forProcessed和 拼接,真正的生成render 函数的部分还是递归调用genElement方法;
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
// v-for 的迭代器,比如例子中的 someArr
const exp = el.for
// 可迭代对象条目名
const alias = el.alias
// iterator 为 v-for="(val, key) in obj" 这种形式是,比如 iterator1 = key
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
// 提示,v-for 指令在组件上时必须使用 key
if (process.env.NODE_ENV !== 'production' &&
state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
// 抛出警告,没有 key
}
// 标记当前节点上的 v-for 被处理过了
el.forProcessed = true // avoid recursion
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` + // 递归调用 genElement()
'})'
}
3.1 genFor 返回值
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${(altGen || genElement)(el, state)}` +
'})'
上面的 genFor 的返回值中用到的各个变量和常量:
altHelper是undefinedalias是itemiterator1、iterator2是空字符串 ''`alterGen是undefinedgenElement(el, state)返回值:"_c('span',{key:index},[_v(_s(item))])"
结合前面的分析,得到 <span v-for="item in someArr" :key="index">{{item}}</span> 得到的渲染函数(render函数):
return `_l(someArr, function (item) { return _c('span', { key: index }, [_v(_s(item))])})`
这里其实我们可以大胆的猜测一下 _l 方法的工作:遍历 someArr,遍历过程中调用 function(item) { _c(...)}, 传入 someArr 的每一项, 最终得到 someArr 每一项对应的 DOM 元素;当然这只是猜测,后面我们会去验证这个猜想;
四、总结
本篇小作文讨论了 Vue 在编译时处理另一个常用指令 —— v-for 的渲染函数生成的方法 genFor,它的主要工作如下:
- 首先明确
genFor是genElement的一个分支流程,用于处理有v-for指令的ast节点; - 标记
el.processed为true,防止genFor递归genElement时进入死循环; - 根据前面
parse阶段所得到的el.for/el.alias/el.iterator1/el.iterator2信息拼接得到v-for的渲染函数代码结构,期间处理生成元素的具体逻辑还是有genElement方法实现的;