这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战
渲染流程中核心部分之一是render
函数的生成,整个过程的核心部分分为两块:
- 通过
parseHTML
函数将HTML
编译成AST
(PS:前文已完成) - 通过
AST
生成render
函数(PS:接下来的重点)
目标
首先明确当我们有下面这段HTML
<div id="app"><p>hi {{msg}}</p> hello</div>
复制代码
通过前文的parseHTML
将其编译为AST
为
其对应生成的render
函数
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
}
}
复制代码
明确了HTML
、AST
和render
函数三者的关联,其三者的整个流程为
function compileToFunction(template) {
// 1. 解析HTML 生成AST
const ast = parseHTML(template);
// optimize(ast, options)
// 2. 根据 AST 生成 render函数
const { render } = generator(ast);
// render 返回的是虚拟DOM
return {
ast,
render,
};
}
复制代码
期望中的generator
函数应该为
function generator(ast) {
// code 待生成
return {
render: new Function(`with(this) {
return ${code}
}`),
};
}
复制代码
其中的code
就是上述的
_c('div', {
attrs: {
"id": "app"
}
}, [_c('p', [_v("hi " + _s(msg))]), _v(" hello")])
复制代码
最终返回的函数使用了with
,其中code
是一段字符串(如上所示),可以得知AST
生成render
函数这个过程的核心:通过递归遍历所有的AST
节点,通过判断AST
节点类型进行对应的字符串拼接
因为Vue
的组件都只能存在一个跟节点,可以直接得到code
的跟节点的tag
const code = `_c(
"${el.tag}"
)`;
复制代码
若是当前的元素节点存在属性时,如下面这段HTML
<div id="app" class="content"></div>
复制代码
将其转换为AST
时其属性的表现形式为
{
attrs: [{
name: 'id',
value: 'app'
}, {
name: 'class',
value: 'content'
}]
}
复制代码
在render
函数中其为
{
attrs: {
'id': 'app',
'class': 'content'
}
}
复制代码
此时需要一个函数,用于处理元素标签上的属性,将其格式转换为render
函数期望的格式
const genProps = (attrs) => {
let str = "";
// 遍历AST上当前元素节点所有的属性
for (let i = 0; i < attrs.length; i++) {
let attr = attrs[i];
// 对于样式 需要特殊处理 -- style="color: aqua;" ==> {style: {color: aqua}}
if (attr.name === "style") {
let obj = {};
// 将样式中的每个属性和属性的值进行切割
// "color: aqua; height: 200px;" ===> ['color: aqua', ' height: 200px']
attr.value.split(";").forEach((item) => {
// 获取属性和属性的值
let [key, value] = item.split(":");
if (key) {
obj[key] = value.trim();
}
});
attr.value = obj;
}
// 将数据进行字符串拼接
str += `${attr.name}: ${JSON.stringify(attr.value)},`;
}
// 返回有拼接完成的字符串
return `{${str.slice(0, -1)}}`;
};
复制代码
此时code
可以进一步完善
const code = `_c(
"${ast.tag}",
${ast.attrs.length ? genProps(ast.attrs) : "undefined"}
)`;
复制代码
至此已经完成了render
函数生成工作的初始化和属性的处理