調(diào)用createApp?時Vue工作過程原理
引言
在使用Vue3時,我們需要使用createApp來創(chuàng)建一個應用實例,然后使用mount方法將應用掛載到某個DOM節(jié)點上。
那么在調(diào)用createApp時,Vue再背后做了些什么事情呢?今天就來扒一扒Vue3的源碼,看看調(diào)用createApp發(fā)生了些什么。
尋找入口
在上一章中,我們我們已經(jīng)將Vue3的源碼下載下來了,并且已經(jīng)知道如何編譯源碼了,先看一下Vue3的源碼目錄:

packages目錄下的包就是Vue3的所有源碼了,編譯之后會在每個工程包下面生成一個dist目錄,里面就是編譯后的文件。
這里我框出了vue包,這個大家都熟悉,打開vue包下的package.json文件,可以看到unpkg字段指向了dist/vue.global.js文件,這個文件就是Vue3的全局版本,我們可以直接在瀏覽器中引入這個文件來使用Vue3。
代碼邏輯基本上都是相同的,用打包后的文件來分析源碼,可以更加直觀的看到源碼的邏輯,因為Vue在設計的時候會考慮其他平臺,如果直接通過源碼來查看會有額外的心智負擔。
具體如何使用每個打包后的文件,可以查看vue包下的README.md文件,如果只是想分析源碼,且不想那么麻煩,可以直接使用dist/vue.global.js文件。
如果想了解Vue3的目錄結(jié)構(gòu)和模塊劃分可以使用vue.esm-bundler.js文件,這個文件是Vue3的ESM版本,會通過import來引入其他模塊,這樣就可以直接看到Vue3的模塊劃分。
本系列就會通過vue.esm-bundler.js文件來分析Vue3的源碼,并且會通過邊分析邊動手的方式來學習Vue3的源碼。

使用
我們先來看一下Vue3的使用方式:
import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
在Vue3中,我們需要使用createApp來創(chuàng)建一個應用實例,然后使用mount方法將應用掛載到某個DOM節(jié)點上。
createApp是從vue包中導出的一個方法,它接收一個組件作為參數(shù),然后返回一個應用實例。
入口 createApp
從vue的package.json可以看到,module字段指向了dist/vue.esm-bundler.js文件,這個文件是Vue3的ESM版本,我們可以直接使用import來引入Vue3。
而createApp方法并不在這個包中,而是在runtime-dom包中,這個文件是直接全部導出runtime-dom包中的內(nèi)容:
export * from '@vue/runtime-dom';
不用懷疑@vue/runtime-dom指向的就是runtime-dom包,使用esm版本就直接找xxx.esm-bundler.js文件,使用cjs版本就直接找xxx.cjs.js文件,后面不會再提到這個問題。
打開runtime-dom.esm-bundler.js文件,可以看到createApp方法:
import { } from '@vue/runtime-core';
export * from '@vue/runtime-core';
import { } from '@vue/shared';
// ... 省略n多代碼
function createApp(...args) {
// ...
}
export {createApp};
可以看到runtime-dom包中還引用了runtime-core包和shared包,現(xiàn)在找到入口文件了,在分析直接可以先搭建一個簡單的代碼分析和測試的環(huán)境,這樣方便自己驗證并且可以直接看到代碼的執(zhí)行結(jié)果。
demo環(huán)境可以直接在本地搭建,也可以使用codesandbox、stackblitz等在線環(huán)境,這里使用codesandbox,后續(xù)demo的代碼都會放在codesandbox上,文末會有鏈接。
當然大家也可以直接在本地搭建一個demo環(huán)境,這里就不再贅述了。
源碼分析
上面的環(huán)境都準備好了之后就可以直接開始分析Vue3的源碼了,我們先來看一下createApp方法的實現(xiàn);
createApp
const createApp = (...args) => {
const app = ensureRenderer().createApp(...args);
const {mount} = app;
app.mount = (containerOrSelector) => {
// ...
};
return app;
}
createApp方法接收一個組件作為參數(shù),然后調(diào)用ensureRenderer方法;
這個方法的作用是確保渲染器存在,如果不存在就創(chuàng)建一個渲染器,然后調(diào)用渲染器的createApp方法,這個方法的作用是創(chuàng)建一個應用實例,然后將這個應用實例返回,相當于一個單例模式。
let renderer; const ensureRenderer = () => renderer || (renderer = createRenderer(rendererOptions));
這里的rendererOptions是一些渲染器的配置,主要的作用是用來操作DOM的,這里不做過多的介紹,后面會有專門的文章來介紹。
現(xiàn)在先簡單的來認識一下rendererOptions,這個里面會有兩個方法后面會用到:
const rendererOptions = {
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null);
},
createText: text => document.createTextNode(text),
}
現(xiàn)在我們先簡單的動手實現(xiàn)一下createApp方法,新建一個runtime-dom.js文件,然后內(nèi)容如下:
import { createRenderer } from "./runtime-core";
const createApp = (...args) => {
const rendererOptions = {
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null);
},
createText: (text) => document.createTextNode(text)
};
const app = createRenderer(rendererOptions).createApp(...args);
const { mount } = app;
app.mount = (containerOrSelector) => {
//...后面分析再補上
};
return app;
};
export { createApp };
現(xiàn)在可以看到我們在實現(xiàn)createApp方法的時候,直接調(diào)用了createRenderer方法,這個方法是創(chuàng)建渲染器的方法,這個方法的實現(xiàn)在runtime-core包中;
所以我們需要補上runtime-core包中的createRenderer方法的實現(xiàn);
createRenderer
createRenderer源碼實現(xiàn)如下:
function createRenderer(options) {
return baseCreateRenderer(options);
}
// implementation
function baseCreateRenderer(options, createHydrationFns) {
// 省略 n 多代碼,都是函數(shù)定義,并會立即執(zhí)行,暫時對結(jié)果不會有影響
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
createRenderer內(nèi)部返回baseCreateRenderer方法的執(zhí)行結(jié)果,這個方法的作用會返回render、hydrate、createApp三個方法;
而我們最后需要調(diào)用的createApp方法就是在這三個方法中的其中一個,而createApp方法的是通過createAppAPI方法創(chuàng)建的,同時剩下的兩個方法render和hydrate也是在createAppAPI方法中被調(diào)用的,所以我們還需要看一下createAppAPI方法的實現(xiàn);
createAppAPI
createAppAPI方法的實現(xiàn)如下:
function createAppContext() {
return {
app: null,
config: {
isNativeTag: NO,
performance: false,
globalProperties: {},
optionMergeStrategies: {},
errorHandler: undefined,
warnHandler: undefined,
compilerOptions: {}
},
mixins: [],
components: {},
directives: {},
provides: Object.create(null),
optionsCache: new WeakMap(),
propsCache: new WeakMap(),
emitsCache: new WeakMap()
};
}
// 這個變量是用來統(tǒng)計創(chuàng)建的應用實例的個數(shù)
let uid$1 = 0;
function createAppAPI(render, hydrate) {
// 返回一個函數(shù),這里主要是通過閉包來緩存上面?zhèn)魅氲膮?shù)
return function createApp(rootComponent, rootProps = null) {
// rootComponent 就是我們傳入的根組件,這里會做一些校驗
// 如果傳遞的不是一個函數(shù),那么就做一個淺拷貝
if (!isFunction(rootComponent)) {
rootComponent = Object.assign({}, rootComponent);
}
// rootProps 就是我們傳入的根組件的 props,這個參數(shù)必須是一個對象
if (rootProps != null && !isObject(rootProps)) {
(process.env.NODE_ENV !== 'production') && warn(`root props passed to app.mount() must be an object.`);
rootProps = null;
}
// 創(chuàng)建上下文對象,在上面定義,就是返回一個對象
const context = createAppContext();
// 通過 use 創(chuàng)建的插件都存在這里
const installedPlugins = new Set();
// 是否已經(jīng)掛載
let isMounted = false;
// 創(chuàng)建 app 對象
const app = (context.app = {
_uid: uid$1++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
version,
get config() {
// ...
},
set config(v) {
// ...
},
use(plugin, ...options) {
// ...
},
mixin(mixin) {
// ...
},
component(name, component) {
// ...
},
directive(name, directive) {
// ...
},
mount(rootContainer, isHydrate, isSVG) {
// ...
},
unmount() {
// ...
},
provide(key, value) {
// ...
}
});
// 返回 app 對象
return app;
};
}
看到這里,我們就可以知道,createApp方法的實現(xiàn)其實就是在createAppAPI方法中返回一個函數(shù),這個函數(shù)就是createApp方法;
這個方法并沒有多么特殊,就是返回了一堆對象,這些對象就是我們在使用createApp方法時,可以調(diào)用的方法;
這里可以看到我們常用的use、mixin、component、directive、mount、unmount、provide等方法都是在app對象上的,也是通過這個函數(shù)制造并返回的;
現(xiàn)在我們繼續(xù)完善我們的學習demo代碼,現(xiàn)在新建一個runtime-core.js文件夾,然后把上面的代碼復制進去;
但是我們不能全都都直接照搬,上面的對象這么多的屬性我們只需要保留mount,因為還需要掛載才能看到效果,demo代碼如下:
function createRenderer(options) {
// 先省略 render 和 hydrate 方法的實現(xiàn),后面會講到
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
function createAppAPI(render, hydrate) {
return function createApp(rootComponent, rootProps = null) {
// 省略參數(shù)校驗
rootComponent = Object.assign({}, rootComponent);
// 省略上下文的創(chuàng)建
const context = {
app: null
}
// 忽略其他函數(shù)的實現(xiàn),只保留 mount 函數(shù)和私有變量
let isMounted = false;
const app = (context.app = {
_uid: uid$1++,
_component: rootComponent,
_props: rootProps,
_container: null,
_context: context,
_instance: null,
mount(rootContainer, isHydrate, isSVG) {
// ...
},
});
return app;
};
}
這樣我們就完成了createApp函數(shù)的簡化版實現(xiàn),接下來我們就可以開始掛載了;
mount 掛載
上面我們已經(jīng)學習到了createApp函數(shù)的實現(xiàn),現(xiàn)在還需要通過mount方法來掛載我們的根組件,才能驗證我們的demo代碼是否正確;
我們在調(diào)用createApp方法時,會返回一個app對象,這個對象上有一個mount方法,我們需要通過這個方法來掛載我們的根組件;
在這之前,我們看到了createApp的實現(xiàn)中重寫了mount方法,如下:
const createApp = (...args) => {
// ...省略其他代碼
// 備份 mount 方法
const { mount } = app;
// 重寫 mount 方法
app.mount = (containerOrSelector) => {
// 獲取掛載的容器
const container = normalizeContainer(containerOrSelector);
if (!container)
return;
// _component 指向的是 createApp 傳入的根組件
const component = app._component;
// 驗證根組件是否是一個對象,并且有 render 和 template 兩個屬性之一
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
// 確保模板是可信的,因為模板可能會有 JS 表達式,具體可以翻譯上面的注釋
component.template = container.innerHTML;
}
// clear content before mounting
// 掛載前清空容器
container.innerHTML = '';
// 正式掛載
const proxy = mount(container, false, container instanceof SVGElement);
// 掛載完成
if (container instanceof Element) {
// 清除容器的 v-cloak 屬性,這也就是我們經(jīng)??吹降?v-cloak 的作用
container.removeAttribute('v-cloak');
// 設置容器的 data-v-app 屬性
container.setAttribute('data-v-app', '');
}
// 返回根組件的實例
return proxy;
};
return app;
}
上面重寫的mount方法中,其實最主要的做的是三件事:
- 獲取掛載的容器
- 調(diào)用原本的
mount方法掛載根組件 - 為容器設置
vue的專屬屬性
現(xiàn)在到我們動手實現(xiàn)一個簡易版的mount方法了;
// 備份 mount 方法
const { mount } = app;
// 重寫 mount 方法
app.mount = (containerOrSelector) => {
// 獲取掛載的容器
const container = document.querySelector(containerOrSelector);
if (!container)
return;
const component = app._component;
container.innerHTML = '';
// 正式掛載
return mount(container, false, container instanceof SVGElement);
};
這里的掛載其實還是使用的是createApp函數(shù)中的mount方法,我們可以看到mount方法的實現(xiàn)如下:
function mount(rootContainer, isHydrate, isSVG) {
// 判斷是否已經(jīng)掛載
if (!isMounted) {
// 這里的 #5571 是一個 issue 的 id,可以在 github 上搜索,這是一個在相同容器上重復掛載的問題,這里只做提示,不做處理
// #5571
if ((process.env.NODE_ENV !== 'production') && rootContainer.__vue_app__) {
warn(`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling `app.unmount()` first.`);
}
// 通過在 createApp 中傳遞的參數(shù)來創(chuàng)建虛擬節(jié)點
const vnode = createVNode(rootComponent, rootProps);
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
// 上面有注釋,在根節(jié)點上掛載 app 上下文,這個上下文會在掛載時設置到根實例上
vnode.appContext = context;
// HMR root reload
// 熱更新
if ((process.env.NODE_ENV !== 'production')) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG);
};
}
// 通過其他的方式掛載,這里不一定指代的是服務端渲染,也可能是其他的方式
// 這一塊可以通過創(chuàng)建渲染器的源碼可以看出,我們?nèi)粘T诳蛻舳虽秩?,不會使用到這一塊,這里只是做提示,不做具體的分析
if (isHydrate && hydrate) {
hydrate(vnode, rootContainer);
}
// 其他情況下,直接通過 render 函數(shù)掛載
// render 函數(shù)在 createRenderer 中定義,傳遞到 createAppAPI 中,通過閉包緩存下來的
else {
render(vnode, rootContainer, isSVG);
}
// 掛載完成后,設置 isMounted 為 true
isMounted = true;
// 設置 app 實例的 _container 屬性,指向掛載的容器
app._container = rootContainer;
// 掛載的容器上掛載 app 實例,也就是說我們可以通過容器找到 app 實例
rootContainer.__vue_app__ = app;
// 非生產(chǎn)環(huán)境默認開啟 devtools,也可以通過全局配置來開啟或關閉
// __VUE_PROD_DEVTOOLS__ 可以通過自己使用的構(gòu)建工具來配置,這里只做提示
if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
app._instance = vnode.component;
devtoolsInitApp(app, version);
}
// 返回 app 實例,這里不做具體的分析
return getExposeProxy(vnode.component) || vnode.component.proxy;
}
// 如果已經(jīng)掛載過則輸出提示消息,在非生產(chǎn)環(huán)境下
else if ((process.env.NODE_ENV !== 'production')) {
warn(`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. `const createMyApp = () => createApp(App)``);
}
}
通過上面的一通分析,其實掛載主要就是用的兩個函數(shù)將內(nèi)容渲染到容器中;
- createVNode 創(chuàng)建虛擬節(jié)點
- render 渲染虛擬節(jié)點
我們這里就實現(xiàn)一個簡易版的mount函數(shù),來模擬掛載過程,代碼如下:
function mount(rootContainer, isHydrate) {
// createApp 中傳遞的參數(shù)在我們這里肯定是一個對象,所以這里不做創(chuàng)建虛擬節(jié)點的操作,而是模擬一個虛擬節(jié)點
const vnode = {
type: rootComponent,
children: [],
component: null,
}
// 通過 render 函數(shù)渲染虛擬節(jié)點
render(vnode, rootContainer);
// 返回 app 實例
return vnode.component
}
虛擬節(jié)點
虛擬節(jié)點在Vue中已經(jīng)是非常常見的概念了,其實就是一個js對象,包含了dom的一些屬性,比如tag、props、children等等;
在Vue3中維護了一套自己的虛擬節(jié)點,大概信息如下:
export interface VNode {
__v_isVNode: true;
__v_skip: true;
type: VNodeTypes;
props: VNodeProps | null;
key: Key | null;
ref: Ref<null> | null;
scopeId: string | null;
children: VNodeNormalizedChildren;
component: ComponentInternalInstance | null;
suspense: SuspenseBoundary | null;
dirs: DirectiveBinding[] | null;
transition: TransitionHooks<null> | null;
el: RendererElement | null;
anchor: RendererNode | null;
target: RendererNode | null;
targetAnchor: RendererNode | null;
staticCount: number;
shapeFlag: ShapeFlags;
patchFlag: number;
dynamicProps: string[] | null;
dynamicChildren: VNode[] | null;
appContext: AppContext | null;
}
完整的type信息太多,這里就只貼VNode的相關定義,而且這些在Vue的實現(xiàn)中也沒有那么簡單,這一章不做具體的分析,只是做一個簡單的概念介紹;
render
render函數(shù)是在講createRenderer的時候出現(xiàn)的,是在baseCreateRenderer中定義的,具體源碼如下:
function baseCreateRenderer(options, createHydrationFns) {
// ...
// 創(chuàng)建 render 函數(shù)
const render = (vnode, container, isSVG) => {
// 如果 vnode 不存在,并且容器是發(fā)生過渲染,那么將執(zhí)行卸載操作
if (vnode == null) {
// container._vnode 指向的是上一次渲染的 vnode,在這個函數(shù)的最后一行
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
}
// 執(zhí)行 patch 操作,這里不做具體的分析,牽扯太大,后面會單獨講
else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG);
}
// 刷新任務隊列,通常指代的是各種回調(diào)函數(shù),比如生命周期函數(shù)、watcher、nextTick 等等
// 這里不做具體的分析,后面會單獨講
flushPreFlushCbs();
flushPostFlushCbs();
// 記錄 vnode,現(xiàn)在的 vnode 已經(jīng)是上一次渲染的 vnode 了
container._vnode = vnode;
};
// ...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
};
}
render函數(shù)的主要作用就是將虛擬節(jié)點渲染到容器中,unmount函數(shù)用來卸載容器中的內(nèi)容,patch函數(shù)用來更新容器中的內(nèi)容;
現(xiàn)在來實現(xiàn)一個簡易版的render函數(shù):
const render = (vnode, container) => {
patch(container._vnode || null, vnode, container);
// 記錄 vnode,現(xiàn)在的 vnode 已經(jīng)是上一次渲染的 vnode 了
container._vnode = vnode;
}
unmount函數(shù)不是我們這次主要學習的內(nèi)容,所以這里不做具體的分析;
patch函數(shù)是Vue中最核心的函數(shù),這次也不做具體的分析,后面會單獨講,但是要驗證我們這次的學習成果,所以我們需要一個只有掛載功能的patch函數(shù),這里我們就自己實現(xiàn)一個簡單的patch函數(shù);
patch
patch函數(shù)的主要作用就是將虛擬節(jié)點渲染到容器中,patch函數(shù)也是在baseCreateRenderer中定義的;
patch函數(shù)這次就不看了,因為內(nèi)部的實現(xiàn)會牽扯到非常多的內(nèi)容,這次只是它的出現(xiàn)只是走個過場,后面會單獨講;
我們這次的目的只是驗證我們這次源碼學習的成成果,所以我們只需要一個只有掛載功能的patch函數(shù),這里我們就自己實現(xiàn)一個簡單的patch函數(shù);
// options 是在創(chuàng)建渲染器的時候傳入的,還記得在 createApp 的實現(xiàn)中,我們傳入了一個有 insert 和 createText 方法的對象嗎?不記得可以往上翻翻
const { insert: hostInsert, createText: hostCreateText} = options;
// Note: functions inside this closure should use `const xxx = () => {}`
// style in order to prevent being inlined by minifiers.
/**
* 簡易版的實現(xiàn),只是刪除了一些不必要的邏輯
* @param n1 上一次渲染的 vnode
* @param n2 當前需要渲染的 vnode
* @param container 容器
* @param anchor 錨點, 用來標記插入的位置
*/
const patch = (n1, n2, container, anchor = null) => {
// 上一次渲染的 vnode 和當前需要渲染的 vnode 是同一個 vnode,那么就不需要做任何操作
if (n1 === n2) {
return;
}
// 獲取當前需要渲染的 vnode 的類型
const { type } = n2;
switch (type) {
// 如果是文本節(jié)點,那么就直接創(chuàng)建文本節(jié)點,然后插入到容器中
case Text:
processText(n1, n2, container, anchor);
break;
// 還會有其他的類型,這里不做具體的分析,后面會單獨講
// 其他的情況也會有很多種情況,這里統(tǒng)一當做是組件處理
default:
processComponent(n1, n2, container, anchor);
}
};
patch函數(shù)的主要作用就是將虛擬節(jié)點正確的渲染到容器中,這里我們只實現(xiàn)了文本節(jié)點和組件的渲染,其他的類型的節(jié)點,后面會單獨講;
而我們在使用createApp的時候,通常會傳入一個根組件,這個根組件就會走到processComponent函數(shù)中;
所以我們這里還需要實現(xiàn)了一個簡單的processComponent函數(shù);
const processComponent = (n1, n2, container, anchor) => {
if (n1 == null) {
mountComponent(n2, container, anchor);
}
// else {
// updateComponent(n1, n2, optimized);
// }
};
processComponent函數(shù)也是定義在baseCreateRenderer中的,這里還是和patch函數(shù)一樣,只是實現(xiàn)了一個簡單的功能,后面會單獨講;
processComponent函數(shù)做了兩件事,一個是掛載組件,一個是更新組件,這里我們只實現(xiàn)了掛載組件的功能;
掛載組件是通過mountComponent函數(shù)實現(xiàn)的,這個函數(shù)也是定義在baseCreateRenderer中的,但是我們這次就不再繼續(xù)深入內(nèi)部調(diào)用了,直接實現(xiàn)一個簡易的:
const mountComponent = (initialVNode, container, anchor) => {
// 通過調(diào)用組件的 render 方法,獲取組件的 vnode
const subTree = initialVNode.type.render.call(null);
// 將組件的 vnode 渲染到容器中,直接調(diào)用 patch 函數(shù)
patch(null, subTree, container, anchor);
};
這樣我們就實現(xiàn)了一個簡易版的掛載組件的功能,這里我們只是簡單的調(diào)用了組件的render方法,render方法會返回一個vnode,然后調(diào)用patch函數(shù)將vnode渲染到容器中;
現(xiàn)在回頭看看patch函數(shù),還差一個processText函數(shù)沒有實現(xiàn),這個函數(shù)也是定義在baseCreateRenderer中的,這個比較簡單,下面的代碼就是實現(xiàn)的processText函數(shù):
const processText = (n1, n2, container, anchor) => {
if (n1 == null) {
hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
}
// else {
// const el = (n2.el = n1.el);
// if (n2.children !== n1.children) {
// hostSetText(el, n2.children);
// }
// }
};
我這里屏蔽掉了更新的操作,這里只管掛載,這里的hostInsert和hostCreateText函數(shù)就是在我們實現(xiàn)簡易patch函數(shù)的時候,在patch函數(shù)實現(xiàn)的上面,通過解構(gòu)賦值獲取的,沒印象可以回去看看;
驗證
現(xiàn)在我們已經(jīng)實現(xiàn)了一個簡易版的createApp函數(shù),并且我們可以通過createApp函數(shù)創(chuàng)建一個應用,然后通過mount方法將應用掛載到容器中;
我們可以通過下面的代碼來驗證一下:
import { createApp } from "./runtime-dom";
const app = createApp({
render() {
return {
type: "Text",
children: "hello world"
};
}
});
app.mount("#app");
源碼在codesandbox上面,可以直接查看:codesandbox.io/s/gallant-s…
總結(jié)
我們通過閱讀Vue3的源碼,了解了Vue3的createApp函數(shù)的實現(xiàn),createApp函數(shù)是Vue3的入口函數(shù),通過createApp函數(shù)我們可以創(chuàng)建一個應用;
createApp的實現(xiàn)是借助了createRenderer函數(shù),createRenderer的實現(xiàn)就是包裝了baseCreateRenderer;
baseCreateRenderer函數(shù)是一個工廠函數(shù),通過baseCreateRenderer函數(shù)我們可以創(chuàng)建一個渲染器;
baseCreateRenderer函數(shù)接收一個options對象,這個options對象中包含了一些渲染器的配置,比如insert、createText等;
這些配置是在runtime-dom中實現(xiàn)的,runtime-dom中的createApp函數(shù)會將這些配置透傳遞給baseCreateRenderer函數(shù),然后baseCreateRenderer函數(shù)會返回一個渲染器,這個渲染器中有一個函數(shù)就是createApp;
createApp函數(shù)接收一個組件,然后返回一個應用,這個應用中有一個mount方法,這個mount方法就是用來將應用掛載到容器中的;
在createApp中重寫了mount方法,內(nèi)部的實現(xiàn)是通過調(diào)用渲染器的mount方法;
這個mount方法是在baseCreateRenderer函數(shù)中實現(xiàn)的,baseCreateRenderer函數(shù)中的mount方法會調(diào)用patch函數(shù);
patch函數(shù)內(nèi)部會做很多的事情,雖然我們這里只實現(xiàn)了掛載的邏輯,但是也是粗窺了patch函數(shù)的內(nèi)部一些邏輯;
最后我們實現(xiàn)了一個精簡版的createApp函數(shù),通過這個函數(shù)我們可以創(chuàng)建一個應用,然后通過mount方法將應用掛載到容器中,這個過程中我們也了解了Vue3的一些實現(xiàn)細節(jié);
以上就是調(diào)用createApp 時Vue工作過程原理的詳細內(nèi)容,更多關于Vue調(diào)用createApp 的資料請關注腳本之家其它相關文章!
相關文章
vue使用原生js實現(xiàn)滾動頁面跟蹤導航高亮的示例代碼
這篇文章主要介紹了vue使用原生js實現(xiàn)滾動頁面跟蹤導航高亮的示例代碼,滾動頁面指定區(qū)域?qū)Ш礁吡痢P【幱X得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-10-10
vue3使用vueup/vue-quill富文本、并限制輸入字數(shù)的方法處理
這篇文章主要介紹了vue3使用vueup/vue-quill富文本、并限制輸入字數(shù),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03
vue3+vite+ts?通過svg-sprite-loader?插件使用自定義圖標的詳細步驟
這篇文章主要介紹了vue3+vite+ts通過svg-sprite-loader插件使用自定義圖標,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09
vue實現(xiàn)token過期自動跳轉(zhuǎn)到登錄頁面
本文主要介紹了vue實現(xiàn)token過期自動跳轉(zhuǎn)到登錄頁面,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10

