一、写在前面
在vue开发中,组件是非常重要的概念,但是我们在编写组件的时候,是否知道其内部是如何进行运转的。本文将总结一下vue3.0中组件是如何进行渲染的?
二、内容
在我们编写组件代码时,会经常编写如下所示模板代码。
<template>
<div>
<p>hello world</p>
</div>
</template>
从上述表现上看,组件的模板决定了组件生成的DOM标签,而在vuejs内部,一个组件如果想要生成真正的DOM,需要经过如下几个步骤。
如上图所示,需要经过创建vnode
,渲染vnode
,以及生成DOM
的三个过程。接下来我们将从程序入口开始,一步一步看真实DOM是如何生成的。
1、应用程序初始化
// 在 Vue.js 3.0 中,初始化一个应用的方式如下
import {
createApp } from "vue";
import App from "./app";
const app = createApp(App);
app.mount("#app");
如上图所示,我们可以看到入口函数是createApp
。
const createApp = (...args) => {
// 创建 app 对象
const app = ensureRenderer().createApp(...args);
const {
mount } = app;
// 重写 mount 方法
app.mount = (containerOrSelector) => {
// ...
};
return app;
};
上述就是createApp
主要做的事,一个是创建app对象
,另一个是重写app.mount
方法。
2、创建app对象
首先我们首先执行代码:
const app = ensureRenderer().createApp(...args);
其中ensureRenderer()
来创建一个渲染器对象,它内部代码为:
// 渲染相关的一些配置,比如更新属性的方法,操作 DOM 的方法
const rendererOptions = {
patchProp,
...nodeOps,
};
let renderer;
// 延时创建渲染器,当用户只依赖响应式包的时候,可以通过 tree-shaking 移除核心渲染逻辑相关的代码
function ensureRenderer() {
return renderer || (renderer = createRenderer(rendererOptions));
}
function createRenderer(options) {
return baseCreateRenderer(options);
}
function baseCreateRenderer(options) {
function render(vnode, container) {
// 组件渲染的核心逻辑
}
return {
render,
createApp: createAppAPI(render),
};
}
function createAppAPI(render) {
// createApp createApp 方法接受的两个参数:根组件的对象和 prop
return function createApp(rootComponent, rootProps = null) {
const app = {
_component: rootComponent,
_props: rootProps,
mount(rootContainer) {
// 创建根组件的 vnode
const vnode = createVNode(rootComponent, rootProps);
// 利用渲染器渲染 vnode
render(vnode, rootContainer);
app._container = rootContainer;
return vnode.component.proxy;
},
};
return app;
};
}
首先ensureRenderer()
来延时创建渲染器。
好处:
当用户值依赖响应式包的时候,就不会创建渲染器。
可以通过tree-shaking的方式来移除核心渲染逻辑相关的代码。
我都其理解是:因为我们在调用createApp才会执行`ensureRenderer()`方法,如果我们只使用响应式的包的时候,并不使用渲染器,此时我们就可以在打包的时候,使用tree-shaking来将没有使用到的函数取消。
其次通过createRenderer创建一个渲染器
,这个渲染器内部存在一个createApp
方法,接收了rootComponent
和rootProps
两个参数。
我们在应用层面执行createAp(App)方法时
,会把App
组件对象作为跟组件传递给rootComponet
,这样createApp
内部就会创建一个App
对象。他会提供mount
方法,这个方法是用来挂载组件的。
值得注意的是:app
在创建对象时,vue
利用闭包和函数的柯里化的技巧,很好的实现了参数保留。
3、重写app.mount方法
createApp
返回的app
兑现已经拥有了mount
方法,那为什么还要重写?