coverPiccoverPic

模板中的 v-if 是怎么编译的(v2)

前言

我们都知道模板编译的大体流程:

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到底发生了什么,要从parseoptimizegenerate三个阶段说起。

parse:生成 AST

在 src/compiler/parser/index.ts,parse调用parseHTML时传入了startend的钩子处理标签开始和结束时的 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
  1. function processIf(el) {
  2. const exp = getAndRemoveAttr(el, 'v-if')
  3. if (exp) {
  4. el.if = exp
  5. addIfCondition(el, {
  6. exp: exp,
  7. block: el
  8. })
  9. } else {
  10. if (getAndRemoveAttr(el, 'v-else') != null) {
  11. el.else = true
  12. }
  13. const elseif = getAndRemoveAttr(el, 'v-else-if')
  14. if (elseif) {
  15. el.elseif = elseif
  16. }
  17. }
  18. }

逻辑很简单,如果是v-ifv-else-if则在el.ifel.elseif中记录表达式,是v-esleel.else为 true。并且像之前说的,在v-if的节点的ifConditions记录所有 if 分支的情况:

ts
  1. export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {
  2. if (!el.ifConditions) {
  3. el.ifConditions = []
  4. }
  5. el.ifConditions.push(condition)
  6. }

el.ifConditions的数据结构:

ts
  1. type ifConditions = ASTIfCondition[]
  2. type ASTIfCondition = {
  3. exp: string | undefined; // v-if,以及后续 v-else-if,v-else 的表达式
  4. block: ASTElement; // v-if,以及后续 v-else-if,v-else 的各分支节点
  5. }

例如,<div v-if="'someExp'"></div>,就有:

js
  1. {
  2. // ...
  3. ifConditions: [
  4. { exp: "'someExp'", block: '[[Circular]]' }
  5. ]
  6. // ...
  7. }

当前v-if节点的ifConditions第 0 项exp记录了v-if的表达式,block为当前 if 分支的 AST 节点,即自己。

end

来看标签结束的逻辑,在closeElement

ts
  1. function closeElement(element) {
  2. // ...
  3. if (!stack.length && element !== root) {
  4. // 处理根元素同级的 if 分支
  5. // allow root elements with v-if, v-else-if and v-else
  6. if (root.if && (element.elseif || element.else)) {
  7. addIfCondition(root, {
  8. exp: element.elseif,
  9. block: element
  10. })
  11. }
  12. }
  13. if (currentParent && !element.forbidden) {
  14. if (element.elseif || element.else) {
  15. processIfConditions(element, currentParent)
  16. } else {
  17. // slot 的逻辑...
  18. currentParent.children.push(element)
  19. element.parent = currentParent
  20. }
  21. }
  22. // ...
  23. }
  1. 有与根标签同级的 if 分支时,记录到根元素的ifConditions中。
  2. v-else-if或者v-else则走processIfConditions移除当前子节点,然后再调用addIfCondition添加到v-if节点的ifConditions
ts
  1. function processIfConditions(el, parent) {
  2. const prev = findPrevElement(parent.children)
  3. if (prev && prev.if) {
  4. addIfCondition(prev, {
  5. exp: el.elseif,
  6. block: el
  7. })
  8. }
  9. }
  10. function findPrevElement(children: Array<any>): ASTElement | void {
  11. let i = children.length
  12. while (i--) {
  13. if (children[i].type === 1) {
  14. return children[i]
  15. } else {
  16. // 要注意,写在 v-if 系列模板标签之间的非标签节点会被忽略
  17. children.pop()
  18. }
  19. }
  20. }

例如:

html
  1. <div v-if="'someExp1'"></div>
  2. <div v-else-if="'someExp2'"></div>
  3. <div v-else></div>

解析完后,看第一个标签的 AST 节点,有:

js
  1. {
  2. // ...
  3. ifConditions: [
  4. { exp: "'someExp1'", block: '[[Circular]]' },
  5. { exp: "'someExp2'", block: {
  6. // ...
  7. elseif: "'someExp2'"
  8. // ...
  9. } },
  10. { exp: undefined, block: {
  11. // ...
  12. else: true
  13. // ...
  14. } },
  15. ]
  16. // ...
  17. }

if 分支的情况都被写在的v-if节点的ifConditions属性上面。

optimize:标记静态节点

src/compiler/optimizer.ts,这部分涉及v-if的地方不多,v-if节点会被视为非静态节点:

ts
  1. function isStatic(node: ASTNode): boolean {
  2. // ...
  3. return !!(
  4. node.pre ||
  5. (!node.hasBindings && // no dynamic bindings
  6. // 看这里!!
  7. !node.if &&
  8. !node.for &&
  9. !isBuiltInTag(node.tag) && // not a built-in
  10. isPlatformReservedTag(node.tag) && // not a component
  11. !isDirectChildOfTemplateFor(node) &&
  12. Object.keys(node).every(isStaticKey))
  13. )
  14. }

在递归遍历 AST 时,也会遍历ifConditions上面的分支:

ts
  1. function markStatic(node: ASTNode) {
  2. // ...
  3. {
  4. if (node.ifConditions) {
  5. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
  6. const block = node.ifConditions[i].block
  7. markStatic(block)
  8. if (!block.static) {
  9. node.static = false
  10. }
  11. }
  12. }
  13. }
  14. }
  15. function markStaticRoots(node: ASTNode, isInFor: boolean) {
  16. if (node.type === 1) {
  17. // ...
  18. if (node.ifConditions) {
  19. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
  20. markStaticRoots(node.ifConditions[i].block, isInFor)
  21. }
  22. }
  23. }
  24. }

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
  1. export function genIf(
  2. el: any,
  3. state: CodegenState,
  4. altGen?: Function,
  5. altEmpty?: string
  6. ): string {
  7. el.ifProcessed = true // avoid recursion
  8. return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
  9. }

标记已经访问过节点后调用genIfConditionsgenIfConditions核心逻辑是遍历genIfConditions,然后生成x ? y : z的三元表达式来表示 if 分支:

ts
  1. function genIfConditions(
  2. conditions: ASTIfConditions,
  3. state: CodegenState,
  4. altGen?: Function,
  5. altEmpty?: string
  6. ): string {
  7. if (!conditions.length) {
  8. return altEmpty || '_e()'
  9. }
  10. const condition = conditions.shift()!
  11. if (condition.exp) {
  12. return `(${condition.exp})?${genTernaryExp(
  13. condition.block
  14. )}:${genIfConditions(conditions, state, altGen, altEmpty)}`
  15. } else {
  16. return `${genTernaryExp(condition.block)}`
  17. }
  18. // v-if with v-once should generate code like (a)?_m(0):_m(1)
  19. function genTernaryExp(el) {
  20. return altGen
  21. ? altGen(el, state)
  22. : el.once
  23. ? genOnce(el, state)
  24. : genElement(el, state)
  25. }
  26. }

例如:

html
  1. <div v-if="'someExp1'"></div>
  2. <div v-else-if="'someExp2'"></div>
  3. <div v-else></div>

可以生成:

js
  1. `('someExp1')?_c('div'):('someExp2')?_c('div'):_c('div')`

'someExp1''someExp2'就是模板中的 if 分支的表达式,_c其实就是调用render传入的生成标签对应的虚拟 DOMVNode的函数。

总结

本文讨论了v-ifv-else-ifv-else在模板编译阶段怎么被转换为 AST 节点的属性的。

0 条评论未登录用户
Ctrl or + Enter 评论
© 2023-2025 LittleRangiferTarandus, All rights reserved.
🌸 Run