coverPiccoverPic

Generate:生成 render 函数(v2)

前言

原文再续,书接上一回,上次讲到optimize函数标记了静态节点,这里就来讲generate发生的事情。还是这张图:

graph LR
A[template 模板]-->|parse|B[AST 抽象语法树]-->|optimize|C[标记静态节点]-->|generate|D[render]-->|return|E[虚拟 DOM]

Vue 组件调用render函数返回模板对应的虚拟 DOM,也就是VNode,上回的都是 AST 节点的事情,generate做的是,把 AST 节点变成render函数。

入口函数

这个函数在 src/compiler/codegen/index.ts,结构也是很简单。

ts
  1. export function generate(
  2. ast: ASTElement | void,
  3. options: CompilerOptions
  4. ): CodegenResult {
  5. const state = new CodegenState(options)
  6. const code = ast
  7. ? ast.tag === 'script'
  8. ? 'null'
  9. : genElement(ast, state)
  10. : '_c("div")'
  11. return {
  12. render: `with(this){return ${code}}`,
  13. staticRenderFns: state.staticRenderFns
  14. }
  15. }

state是一些进行数据转换的函数,下面遇到再说,这里主要关注标签和文本是怎么被渲染的。with(this){return ${code}}这里的this很明显是指 Vue 组件实例,genElement中生成了render的主要代码。

生成 render 函数

先来看genElement

ts
  1. export function genElement(el: ASTElement, state: CodegenState): string {
  2. // v-if、v-for 等指令的处理,节点静态提升等等...
  3. {
  4. let code
  5. if (el.component) {
  6. // 组件的处理,以后再说...
  7. code = genComponent(el.component, el, state)
  8. } else {
  9. let data
  10. const maybeComponent = state.maybeComponent(el)
  11. if (!el.plain || (el.pre && maybeComponent)) {
  12. data = genData(el, state)
  13. }
  14. // check if this is a component in <script setup> ...
  15. const children = el.inlineTemplate ? null : genChildren(el, state, true)
  16. code = `_c(${tag}${
  17. data ? `,${data}` : '' // data
  18. }${
  19. children ? `,${children}` : '' // children
  20. })`
  21. }
  22. // module transforms
  23. for (let i = 0; i < state.transforms.length; i++) {
  24. code = state.transforms[i](el, code)
  25. }
  26. return code
  27. }
  28. }

data = genData(el, state)genData这个函数返回一个对象的 JSON 字符串,记录了模板上往标签上写的各种属性和事件监听,例如:

html
  1. <div
  2. data="test"
  3. ></div>

data就是:

json
  1. {attrs:{"data":"test"}}

genChildren遍历子元素,最终对于标签、注释、文本 3 种类型的子元素分别调用genElement(递归回去,继续遍历子节点)、genCommentgenText

ts
  1. export function genText(text: ASTText | ASTExpression): string {
  2. return `_v(${
  3. text.type === 2
  4. ? text.expression // no need for () because already wrapped in _s()
  5. : transformSpecialNewlines(JSON.stringify(text.text))
  6. })`
  7. }
ts
  1. export function genComment(comment: ASTText): string {
  2. return `_e(${JSON.stringify(comment.text)})`
  3. }

如果有这样子的标签:

html
  1. <div data="test"><span>123</span></div>

genElement生成这样子的函数片段:

ts
  1. _c('div',{attrs:{"data":"test"}},[_c('span',[_v("123")])])

其中,_c是一个生成标签形式 VNode 的函数,而_v处理文本节点,上文的_e自然是处理注释节点的函数。至于具体是什么,下次再说。

总结

本篇文章介绍了模板编译三大阶段的最后一个阶段——generate阶段。

genElementgenChildren的递归调用下,generate把模板对应的 AST 节点转换为render函数。

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