基于之前的运行时runtime、渲染器render以及编译器compile,其实Vue3的核心逻辑模块都梳理完成了,最后要做的一步就是整合它们,以实现真实Vue3使用时的样子
实际使用Vue3的时候,大概是这么用的
const { createApp } = Vue
const APP = {
template: `<div>hello world</div>`
}
const app = createApp(APP)
app.mount('#app')
这里的过程涉及两部分
app
实例创建- 模板
template
编译成render
函数 - 模板
template
渲染过程
- 模板
app
实例挂载
app实例的创建createApp
app
实例的创建核心就是createApp
方法,这个方法从结果上来看,返回了app
实例,并且这个实例上面有mount
方法,可以将实例挂载在指定根节点上
如果先不考虑编译过程,实际上做的逻辑是下面这样的,即执行了APP
这个组件的render
函数,并把render
渲染出来的内容挂载到app
这个根节点上
const { createApp, h } = Vue;
const APP = {
render() {
return h("div", "hello world");
},
};
const app = createApp(APP);
app.mount("#app");
阅读Vue3源码可以知道,这个createApp
其实调用了createAppAPI
方法
createAppAPI
方法内部返回了一个createApp
方法,构建了app
实例并返回
app
实例中有mount
方法,而mount
方法的核心则是创建出vnode
并调用render
完成渲染
function createAppAPI(render) {
return function createApp(rootComponent, rootProps = null) {
const app = {
_component: rootComponent,
_container: null,
// mount方法本质就是创建vnode并调用render渲染
mount(rootContainer) {
const vnode = createVNode(rootComponent, rootProps, null);
render(vnode, rootContainer);
},
};
return app;
};
}
// 源码中createApp方法和render放一起
function baseCreateRenderer(options: RendererOptions): any {
......
return {
render,
createApp: createAppAPI(render)
}
}
导出逻辑
createApp
方法的导出逻辑也参考了render
方法的,使用ensureRender
来导出
export const createApp = (...args) => {
const app = ensureRenderer().createApp(...args);
return app;
};
选择器处理
在app实例调用mount
方法时候,传入的参数就是要挂载到的根节点,应该是一个DOM容器
但是我们并不一定直接传入一个DOM容器
,也可能传入一个DOM选择器
,这时候就要对DOM选择器
做额外处理
export const createApp = (...args) => {
const app = ensureRenderer().createApp(...args);
// 对mount方法做改写,适配不同平台
const { mount } = app;
app.mount = (containerOrSelector: Element | string) => {
const container = normalizeContainer(containerOrSelector);
if (!container) return;
mount(container);
};
return app;
};
// 传入的可以是选择器或者容器,最后统一返回容器
function normalizeContainer(container: Element | string): Element | null {
if (isString(container)) {
const res = document.querySelector(container);
return res;
}
return container;
}
app实例中的模板编译
createApp方法创建好后,最后只缺一步,就是把模板template
给编译成render
函数
在之前的文章中,我们实现渲染器render
和编译器compile
是分别进行的,二者没有一个联动,所以这里我们只需要建立一个关联就行了
这个关联的地方就在finishComponentSetup
函数中
因为这里把组件中的render
取出,挂载到实例的render
上,如果没有render
则报错,所以在这里处理一下只有template
的情况
function finishComponentSetup(instance) {
const Component = instance.type;
if (!instance.render) {
// 如果有编译器而且没有render方法,那就是要根据模板生成render方法
if (compile && !Component.render) {
// 有模板的时候,根据模板生成render函数并挂载到Component上
if (Component.template) {
const template = Component.template;
Component.render = compile(template);
}
}
instance.render = Component.render;
}
applyOptions(instance);
}
compile
方法在Vue源码中不是直接简单绑定baseCompile
的,而是全局创建了一个compile
变量,并执行了一个registerRuntimeCompile
方法创建
let compile: any = null;
function registerRuntimeCompile(_compile: any) {
compile = _compile;
}
// 这个compileToFunction就是之前把render函数字符串转换成render函数的方法
function compileToFunction(template, options?) {
const { code } = compile(template, options);
const render = new Function(code)();
return render;
}
// 一旦执行过上面的方法,识别到这个文件,runtime的compile就会注册了
registerRuntimeCompile(compileToFunction);