前言
我们都知道模板编译的大体流程:
graph LR O[baseCompile] -->|"①"| B[parse\n生成抽象语法树] A[template 模板]-->|args|B-->|return|C[ast] O-->|"②"|D[optimize\n标记静态节点] C-->|args|D C-->|args|E O-->|"③"|E["generate\n生成 render 函数"]-->|return|F[render]
分析v-if到底发生了什么,要从parse、optimize、generate三个阶段说起。
parse:生成 AST
在 src/compiler/parser/index.ts,parse调用parseHTML时传入了start、end的钩子处理标签开始和结束时的 AST 节点,下面标出涉及v-if系列指令的逻辑:
graph TB C[parse] C-->|call|D A[start]-->|args|D B[end]-->|args|D D[parseHTML]-->|标签开始|A A-->|若为单标签|B D-->|标签结束|B A-->|"v-if、v-else、v-else-if"|E[processIf\n记录 if 的标记]-->|"v-if"|F[addIfCondition\n添加分支到 v-if 节点的 ifConditions] B-->G[closeElement]-->|"v-else、v-else-if"|H[processIfConditions\n寻找前面 v-if 节点] G-->|"与根节点同级的 if 分支"|F H-->F
start
在processIf做的可以理解为初始化节点:
ts- function processIf(el) {
- const exp = getAndRemoveAttr(el, 'v-if')
- if (exp) {
- el.if = exp
- addIfCondition(el, {
- exp: exp,
- block: el
- })
- } else {
- if (getAndRemoveAttr(el, 'v-else') != null) {
- el.else = true
- }
- const elseif = getAndRemoveAttr(el, 'v-else-if')
- if (elseif) {
- el.elseif = elseif
- }
- }
- }
逻辑很简单,如果是v-if、v-else-if则在el.if和el.elseif中记录表达式,是v-esle则el.else为 true。并且像之前说的,在v-if的节点的ifConditions记录所有 if 分支的情况:
ts- export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
- if (!el.ifConditions) {
- el.ifConditions = []
- }
- el.ifConditions.push(condition)
- }
el.ifConditions的数据结构:
ts- type ifConditions = ASTIfCondition[]
- type ASTIfCondition = {
- exp: string | undefined; // v-if,以及后续 v-else-if,v-else 的表达式
- block: ASTElement; // v-if,以及后续 v-else-if,v-else 的各分支节点
- }
例如,<div v-if="'someExp'"></div>,就有:
js- {
- // ...
- ifConditions: [
- { exp: "'someExp'", block: '[[Circular]]' }
- ]
- // ...
- }
当前v-if节点的ifConditions第 0 项exp记录了v-if的表达式,block为当前 if 分支的 AST 节点,即自己。
end
来看标签结束的逻辑,在closeElement:
ts- function closeElement(element) {
- // ...
- if (!stack.length && element !== root) {
- // 处理根元素同级的 if 分支
- // allow root elements with v-if, v-else-if and v-else
- if (root.if && (element.elseif || element.else)) {
- addIfCondition(root, {
- exp: element.elseif,
- block: element
- })
- }
- }
- if (currentParent && !element.forbidden) {
- if (element.elseif || element.else) {
- processIfConditions(element, currentParent)
- } else {
- // slot 的逻辑...
-
- currentParent.children.push(element)
- element.parent = currentParent
- }
- }
- // ...
- }
- 有与根标签同级的 if 分支时,记录到根元素的
ifConditions中。 - 是
v-else-if或者v-else则走processIfConditions移除当前子节点,然后再调用addIfCondition添加到v-if节点的ifConditions:
ts- function processIfConditions(el, parent) {
- const prev = findPrevElement(parent.children)
- if (prev && prev.if) {
- addIfCondition(prev, {
- exp: el.elseif,
- block: el
- })
- }
- }
- function findPrevElement(children: Array<any>): ASTElement | void {
- let i = children.length
- while (i--) {
- if (children[i].type === 1) {
- return children[i]
- } else {
- // 要注意,写在 v-if 系列模板标签之间的非标签节点会被忽略
- children.pop()
- }
- }
- }
例如:
html- <div v-if="'someExp1'"></div>
- <div v-else-if="'someExp2'"></div>
- <div v-else></div>
解析完后,看第一个标签的 AST 节点,有:
js- {
- // ...
- ifConditions: [
- { exp: "'someExp1'", block: '[[Circular]]' },
- { exp: "'someExp2'", block: {
- // ...
- elseif: "'someExp2'"
- // ...
- } },
- { exp: undefined, block: {
- // ...
- else: true
- // ...
- } },
- ]
- // ...
- }
if 分支的情况都被写在的v-if节点的ifConditions属性上面。
optimize:标记静态节点
src/compiler/optimizer.ts,这部分涉及v-if的地方不多,v-if节点会被视为非静态节点:
ts- function isStatic(node: ASTNode): boolean {
- // ...
- return !!(
- node.pre ||
- (!node.hasBindings && // no dynamic bindings
- // 看这里!!
- !node.if &&
- !node.for &&
- !isBuiltInTag(node.tag) && // not a built-in
- isPlatformReservedTag(node.tag) && // not a component
- !isDirectChildOfTemplateFor(node) &&
- Object.keys(node).every(isStaticKey))
- )
- }
在递归遍历 AST 时,也会遍历ifConditions上面的分支:
ts- function markStatic(node: ASTNode) {
- // ...
- {
- if (node.ifConditions) {
- for (let i = 1, l = node.ifConditions.length; i < l; i++) {
- const block = node.ifConditions[i].block
- markStatic(block)
- if (!block.static) {
- node.static = false
- }
- }
- }
- }
- }
- function markStaticRoots(node: ASTNode, isInFor: boolean) {
- if (node.type === 1) {
- // ...
- if (node.ifConditions) {
- for (let i = 1, l = node.ifConditions.length; i < l; i++) {
- markStaticRoots(node.ifConditions[i].block, isInFor)
- }
- }
- }
- }
generate:生成 render 函数
src/compiler/codegen/index.ts,涉及到v-if的流程:
graph TB A[generate]-->|call|B[genElement]-->|"el.if && !el.ifProcessed"|C[genIf]-->|call|D[genIfConditions\n递归调用遍历 ifConditions]-->|"condition.exp"|D D-->|else|E[genTernaryExp\n递归返回 genElement 或者 genOnce]-->|else|A E-->|"el.once"|F["genOnce\nv-once的逻辑,在此不表"]
先来看genIf:
ts- export function genIf(
- el: any,
- state: CodegenState,
- altGen?: Function,
- altEmpty?: string
- ): string {
- el.ifProcessed = true // avoid recursion
- return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
- }
标记已经访问过节点后调用genIfConditions。genIfConditions核心逻辑是遍历genIfConditions,然后生成x ? y : z的三元表达式来表示 if 分支:
ts- function genIfConditions(
- conditions: ASTIfConditions,
- state: CodegenState,
- altGen?: Function,
- altEmpty?: string
- ): string {
- if (!conditions.length) {
- return altEmpty || '_e()'
- }
- const condition = conditions.shift()!
- if (condition.exp) {
- return `(${condition.exp})?${genTernaryExp(
- condition.block
- )}:${genIfConditions(conditions, state, altGen, altEmpty)}`
- } else {
- return `${genTernaryExp(condition.block)}`
- }
- // v-if with v-once should generate code like (a)?_m(0):_m(1)
- function genTernaryExp(el) {
- return altGen
- ? altGen(el, state)
- : el.once
- ? genOnce(el, state)
- : genElement(el, state)
- }
- }
例如:
html- <div v-if="'someExp1'"></div>
- <div v-else-if="'someExp2'"></div>
- <div v-else></div>
可以生成:
js- `('someExp1')?_c('div'):('someExp2')?_c('div'):_c('div')`
'someExp1'、'someExp2'就是模板中的 if 分支的表达式,_c其实就是调用render传入的生成标签对应的虚拟 DOMVNode的函数。
总结
本文讨论了v-if、v-else-if、v-else在模板编译阶段怎么被转换为 AST 节点的属性的。
