今天的文章打算学习下 Vue3 下的模板编译与 Vue2 下的差异,以及 VDOM 下 Diff 算法的优化。
编译入口
了解过 Vue3 的同学肯定知道 Vue3 引入了新的组合 Api,在组件 mount
阶段会调用 setup
方法,之后会判断 render
方法是否存在,如果不存在会调用 compile
方法将 template
转化为 render
。
// packages/runtime-core/src/renderer.ts
const mountComponent = (initialVNode, container) => {
const instance = (
initialVNode.component = createComponentInstance(
// ...params
)
)
// 调用 setup
setupComponent(instance)
}
// packages/runtime-core/src/component.ts
let compile
export function registerRuntimeCompiler(_compile) {
compile = _compile
}
export function setupComponent(instance) {
const Component = instance.type
const {
setup } = Component
if (setup) {
// ...调用 setup
}
if (compile && Component.template && !Component.render) {
// 如果没有 render 方法
// 调用 compile 将 template 转为 render 方法
Component.render = compile(Component.template, {
...})
}
}
这部分都是 runtime-core 中的代码,之前的文章有讲过 Vue 分为完整版和 runtime 版本。如果使用 vue-loader
处理 .vue
文件,一般都会将 .vue
文件中的 template
直接处理成 render
方法。
// 需要编译器
Vue.createApp({
template: '<div>{
{ hi }}</div>'
})
// 不需要
Vue.createApp({
render() {
return Vue.h('div', {
}, this.hi)
}
})
完整版与 runtime 版的差异就是,完整版会引入 compile
方法,如果是 vue-cli 生成的项目就会抹去这部分代码,将 compile 过程都放到打包的阶段,以此优化性能。runtime-dom 中提供了 registerRuntimeCompiler
方法用于注入 compile
方法。
主流程
在完整版的 index.js
中,调用了 registerRuntimeCompiler
将 compile
进行注入,接下来我们看看注入的 compile
方法主要做了什么。
// packages/vue/src/index.ts
import {
compile } from '@vue/compiler-dom'
// 编译缓存
const compileCache = Object.create(null)
// 注入 compile 方法
function compileToFunction(
// 模板
template: string | HTMLElement, // 编译配置
options?: CompilerOptions
): RenderFunction {
if (!isString(template)) {
// 如果 template 不是字符串
// 则认为是一个 DOM 节点,获取 innerHTML
if (template.nodeType) {
template = template.innerHTML
} else {
return NOOP
}
}
// 如果缓存中存在,直接从缓存中获取