代码生成器的作用是将AST转换成渲染函数中的内容,也就是代码字符串,代码字符串被渲染函数执行createElement后生成VNode,虚拟DOM通过VNode的渲染视图。
举个例子:
模板
<div id="el">Hello {{name}}</div>
转成AST后:
{
'type': 1,
'tag': 'div',
'attrsList': [
{
'name': 'id',
'value': 'el'
}
],
'attrsMap': {
'id': 'el'
},
'children': [
{
'type': 2,
'expression': '"Hello "+_s(name)',
'text': 'Hello {{name}}',
'static': false
}
],
'plain': false,
'attrs': [
{
'name': 'id',
'value': '"el"'
}
],
'static': false,
'staticRoot': false
}
生成代码字符串后:
'with(this){return _c("div",{attrs:{"id":"el"}},[_v("Hello "+_s(name))])}'
# 通过AST生成代码字符串
| 类型 | 创建方法 | 别名 |
|---|---|---|
| 元素节点 | createElement | _c |
| 文本节点 | createTextVNode | _v |
| 注释节点 | createEmptyVNode | _e |
例子:
<div id="el">
<div>
<p>Hello {{name}}</p>
</div>
</div>
生成的格式:
_c('div',{attrs:{"id":"el"}},[_c('div',[_c('p',[_v("Hello "+_s(name))])])])
# 代码生成器的原理
# 元素节点
function genElement (el, state) {
// 如果el.plain是true,则说明节点没有属性
const data = el.plain ? undefined : genData(el, state)
const children = genChildren(el, state)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
return code
}
function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// key
if (el.key) {
data += `key:${el.key},`
}
// ref
if (el.ref) {
data += `ref:${el.ref},`
}
// pre
if (el.pre) {
data += `pre:true,`
}
// 类似的还有很多种情况
data = data.replace(/,$/, '') + '}'
return data
}
function genChildren (el, state) {
const children = el.children
if (children.length) {
return `[${children.map(c => genNode(c, state)).join(',')}]`
}
}
// 调用不同节点类型的生成方法来生成字符串
function genNode (node, state) {
if (node.type === 1) {
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
通过genData生成data的过程:先给data赋值一个 '{',然后发现节点存在哪些属性数据,就将这些数据拼接到data中,最后拼接一个 '}',此时一个完整的data就拼好了
生成子节点列表字符串的逻辑也是拼字符串。通过循环子节点列表,根据不同的子节点类型生成不同的节点字符串并将其拼接到一起
# 文本节点
文本放在 _v这个函数的参数中
function genText (text) {
// 如果是动态文本,则使用expression;如果是静态文本,则使用text。
// JSON.stringify可以给文本包装一层字符串
return `_v(${text.type === 2
? text.expression
: JSON.stringify(text.text)
})`
}
# 注释节点
注释拼接到函数 _e的参数中
function genComment (comment) {
return `_e(${JSON.stringify(comment.text)})`
}
# 总结
本章中,我们介绍了代码生成器的作用及其内部原理,了解了代码生成器其实就是字符串拼接的过程。通过递归AST来生成字符串,最先生成根节点,然后在子节点字符串生成后,将其拼接在根节点的参数中,子节点的子节点拼接在子节点的参数中,这样一层一层地拼接,直到最后拼接成完整的字符串。
同时还介绍了三种类型的节点,分别是元素节点、文本节点与注释节点。而不同类型的节点生成字符串的方式是不同的。
最后,我们介绍了当字符串拼接好后,会将字符串拼在with中返回给调用者。
← 优化器 实例方法和全局API的实现 →