上篇文章说到了模板解析的第一步parse
,现在来说第最后一步generate
,用于生成render
函数。这一步的代码基本都放在了src/compiler/codegen/index.js
中,初看一下会觉得非常长,但并没有想象中复杂,只是需要处理的情况比较多。
在这篇文章里只会介绍最简单的处理,目的是了解整个generate
的大致思路。其他的分支会在后续的其他文章里逐步介绍。同时最后会把整个源码注释放到附件里。
理解generate
的处理过程最好是自己打断点一步步调试,这样才不会被茫茫多的处理分支弄晕。这篇文章的采用的测试代码是:
1 | <div id="app"> |
在generate
之前生成的 AST 为:
1 | { |
入口
入口函数很简短:
1 | export function generate(ast: ASTElement | void, options: CompilerOptions): CodegenResult { |
可以看出来最主要的处理逻辑是在genElement
这个函数。generate
的返回结果包含两部分:render
字符串和staticRenderFns
数组,后者是专门用于生成静态内容的,上篇文章也说到了optimizer
部分对于静态内容的优化处理,主要是给静态 ast 节点添加了static
和staticRoot
两个属性。
genElement
这个函数的分支很多,不过我们的示范代码中涉及到的分支比较少,这里做一下精简:
1 | export function genElement(el: ASTElement, state: CodegenState): string { |
genStatic
用于生成渲染静态内容的字符串。
1 | // hoist static sub-trees out |
在我们示范里,el.staticInFor
为false
。 _m
是renderStatic
函数的缩写:
renderStatic
1 | export function renderStatic(index: number, isInFor: boolean): VNode | Array<VNode> { |
可以看到renderStatic
会尽量复用已有的静态内容生成字符串,而不是每次重新调用staticRenderFns
中的函数重新生成,这里算是一个性能优化,因为静态节点的内容始终是不变的。
genData、genChildren
处理完静态内容,接下来就会到else
分支:
1 | // component or element |
除了两个子函数genData
和genChildren
外,其他代码都很好理解,也加了足够多的注释,其中的_c
是在源码解析三
那篇文章里有说到,定义是:
1 | / bind the createElement fn to this instance |
genData
这个函数用来收集节点上的各种属性,最后生成一个对象字符串。函数处理的情况也很多,因为属性的种类很多,这里我们照样做一下剪枝:
1 | function genData (el: ASTElement): string { |
genProps
1 | function genProps(props: Array<{ name: string, value: any }>): string { |
代码还是很简单的,若入参props
为[{name: "id",value:"app"},{name:"type",value:"123"}]
, 则最后genProps
返回的字符串格式类似于"id": "app","type": "123"
。
genChildren
转换完属性就是对children
的操作:
1 | const children = el.inlineTemplate ? null : genChildren(el, state, true); |
不考虑inlineTemplate
,这里我们会走到genChildren(el, state, true)
。
1 | export function genChildren( |
如果el
上存在v-for
,且el.children
只有一个元素时,这里直接返回genElement(el)
.
最后会遍历所有 children,生成他们对应的字符串,然后连接起来。
normalizationType
的意义在注释里已经说了,看看如何决定它的值的:
1 | // determine the normalization needed for the children array. |
最后的genNode
:
1 | function genNode(node: ASTNode, state: CodegenState): string { |
大概瞄一眼genComment
和genText
,这俩的逻辑都很简单。
genComment
1 | export function genComment(comment: ASTText): string { |
_e
用于创建一个空节点,大名是createEmptyVNode
:
1 | export const createEmptyVNode = (text: string = '') => { |
genText
1 | export function genText(text: ASTText | ASTExpression): string { |
_v
是创建一个文本结点,大名是createTextVNode
:
1 | export function createTextVNode(val: string | number) { |
最终我们的示范代码里所涉及到的逻辑都讲完了,看看最后generate
返回的对象,这里我将他们格式化以方便理解:
render:
1 | with (this) { |
staticRenderFns:
1 | with (this) { |