天天看點

「前端」Vue3源碼分析:深入了解Vue3中createApp函數的實作

作者:架構思考

在使用Vue3時,我們需要使用createApp來建立一個應用執行個體,然後使用mount方法将應用挂載到某個DOM節點上。那麼在調用createApp時,Vue在背後做了些什麼事情呢?今天就來扒一扒Vue3的源碼,看看調用createApp發生了些什麼。

尋找入口

先看一下Vue3的源碼目錄:

「前端」Vue3源碼分析:深入了解Vue3中createApp函數的實作

packages目錄下的包就是Vue3的所有源碼了,編譯之後會在每個工程包下面生成一個dist目錄,裡面就是編譯後的檔案。這裡我框出了vue包,這個大家都熟悉,打開vue包下的package.json檔案,可以看到unpkg字段指向了dist/vue.global.js檔案,這個檔案就是Vue3的全局版本,我們可以直接在浏覽器中引入這個檔案來使用Vue3。

代碼邏輯基本上都是相同的,用打包後的檔案來分析源碼,可以更加直覺的看到源碼的邏輯,因為Vue在設計的時候會考慮其他平台,如果直接通過源碼來檢視會有額外的心智負擔。具體如何使用每個打包後的檔案,可以檢視vue包下的README.md檔案,如果隻是想分析源碼,且不想那麼麻煩,可以直接使用dist/vue.global.js檔案。

如果想了解Vue3的目錄結構和子產品劃分可以使用vue.esm-bundler.js檔案,這個檔案是Vue3的ESM版本,會通過import來引入其他子產品,這樣就可以直接看到Vue3的子產品劃分。本系列就會通過vue.esm-bundler.js檔案來分析Vue3的源碼,并且會通過邊分析邊動手的方式來學習Vue3的源碼。

「前端」Vue3源碼分析:深入了解Vue3中createApp函數的實作

使用

我們先來看一下Vue3的使用方式:

import {createApp} from 'vue'
    import App from './App.vue'
    const app = createApp(App)
    app.mount('#app')           

在Vue3中,我們需要使用createApp來建立一個應用執行個體,然後使用mount方法将應用挂載到某個DOM節點上。createApp是從vue包中導出的一個方法,它接收一個元件作為參數,然後傳回一個應用執行個體。

入口 createApp

從vue的package.json可以看到,module字段指向了dist/vue.esm-bundler.js檔案,這個檔案是Vue3的ESM版本,我們可以直接使用import來引入Vue3。而createApp方法并不在這個包中,而是在runtime-dom包中,這個檔案是直接全部導出runtime-dom包中的内容:

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';
    // 堆代碼 duidaima.com
    // ... 省略n多代碼
    function createApp(...args) {
        // ...
    }
    export {createApp};           

可以看到runtime-dom包中還引用了runtime-core包和shared包,現在找到入口檔案了,在分析直接可以先搭建一個簡單的代碼分析和測試的環境,這樣友善自己驗證并且可以直接看到代碼的執行結果。demo環境可以直接在本地搭建,也可以使用codesandbox、stackblitz等線上環境,這裡使用codesandbox,後續demo的代碼都會放在codesandbox上,文末會有連結。

當然大家也可以直接在本地搭建一個demo環境,這裡就不再贅述了。

源碼分析

上面的環境都準備好了之後就可以直接開始分析Vue3的源碼了,我們先來看一下createApp方法的實作;

createApp
    const createApp = (...args) => {
        const app = ensureRenderer().createApp(...args);
       // 堆代碼 duidaima.com
        const {mount} = app;
        app.mount = (containerOrSelector) => {
            // ...
        };
        return app;
    }           

createApp方法接收一個元件作為參數,然後調用ensureRenderer方法;這個方法的作用是確定渲染器存在,如果不存在就建立一個渲染器,然後調用渲染器的createApp方法,這個方法的作用是建立一個應用執行個體,然後将這個應用執行個體傳回,相當于一個單例模式。

let renderer;
    const ensureRenderer = () => renderer || (renderer = createRenderer(rendererOptions));           

這裡的rendererOptions是一些渲染器的配置,主要的作用是用來操作DOM的,這裡不做過多的介紹,後面會有專門的文章來介紹。現在先簡單的來認識一下rendererOptions,這個裡面會有兩個方法後面會用到:

const rendererOptions = {
        insert: (child, parent, anchor) => {
            parent.insertBefore(child, anchor || null);
        },
        createText: text => document.createTextNode(text),
    }           

現在我們先簡單的動手實作一下createApp方法,建立一個runtime-dom.js檔案,然後内容如下:

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 };           

現在可以看到我們在實作createApp方法的時候,直接調用了createRenderer方法,這個方法是建立渲染器的方法,這個方法的實作在runtime-core包中;是以我們需要補上runtime-core包中的createRenderer方法的實作;

createRenderer

createRenderer源碼實作如下:

function createRenderer(options) {
        return baseCreateRenderer(options);
    }
    // implementation
    function baseCreateRenderer(options, createHydrationFns) {
        // 省略 n 多代碼,都是函數定義,并會立即執行,暫時對結果不會有影響
        
        return {
            render,
            hydrate,
            createApp: createAppAPI(render, hydrate)
        };
    }           

createRenderer内部傳回baseCreateRenderer方法的執行結果,這個方法的作用會傳回render、hydrate、createApp三個方法;而我們最後需要調用的createApp方法就是在這三個方法中的其中一個,而createApp方法的是通過createAppAPI方法建立的,同時剩下的兩個方法render和hydrate也是在createAppAPI方法中被調用的,是以我們還需要看一下createAppAPI方法的實作;

createAppAPI

createAppAPI方法的實作如下:

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()
        };
    }
    // 這個變量是用來統計建立的應用執行個體的個數
    let uid$1 = 0;
    function createAppAPI(render, hydrate) {
        // 傳回一個函數,這裡主要是通過閉包來緩存上面傳入的參數
        return function createApp(rootComponent, rootProps = null) {
            // rootComponent 就是我們傳入的根元件,這裡會做一些校驗
            // 堆代碼 duidaima.com
            // 如果傳遞的不是一個函數,那麼就做一個淺拷貝
            if (!isFunction(rootComponent)) {
                rootComponent = Object.assign({}, rootComponent);
            }
            
            // rootProps 就是我們傳入的根元件的 props,這個參數必須是一個對象
            if (rootProps != null && !isObject(rootProps)) {
                (process.env.NODE_ENV !== 'production') && warn(`root props passed to app.mount() must be an object.`);
                rootProps = null;
            }
            
            // 建立上下文對象,在上面定義,就是傳回一個對象
            const context = createAppContext();
            
            // 通過 use 建立的插件都存在這裡
            const installedPlugins = new Set();
            
            // 是否已經挂載
            let isMounted = false;
            
            // 建立 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方法的實作其實就是在createAppAPI方法中傳回一個函數,這個函數就是createApp方法。這個方法并沒有多麼特殊,就是傳回了一堆對象,這些對象就是我們在使用createApp方法時,可以調用的方法。這裡可以看到我們常用的use、mixin、component、directive、mount、unmount、provide等方法都是在app對象上的,也是通過這個函數制造并傳回的。

現在我們繼續完善我們的學習demo代碼,現在建立一個runtime-core.js檔案夾,然後把上面的代碼複制進去,但是我們不能全都都直接照搬,上面的對象這麼多的屬性我們隻需要保留mount,因為還需要挂載才能看到效果,demo代碼如下:

function createRenderer(options) {
        // 先省略 render 和 hydrate 方法的實作,後面會講到
        
       return {
            render,
            hydrate,
            createApp: createAppAPI(render, hydrate)
        };
    }
    function createAppAPI(render, hydrate) {
        return function createApp(rootComponent, rootProps = null) {
            // 省略參數校驗
            rootComponent = Object.assign({}, rootComponent);
            
            // 省略上下文的建立
            const context = {
                app: null
            }
            
            // 忽略其他函數的實作,隻保留 mount 函數和私有變量
            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函數的簡化版實作,接下來我們就可以開始挂載了;

mount 挂載

上面我們已經學習到了createApp函數的實作,現在還需要通過mount方法來挂載我們的根元件,才能驗證我們的demo代碼是否正确,我們在調用createApp方法時,會傳回一個app對象,這個對象上有一個mount方法,我們需要通過這個方法來挂載我們的根元件,在這之前,我們看到了createApp的實作中重寫了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 屬性,這也就是我們經常看到的 v-cloak 的作用
                container.removeAttribute('v-cloak');
                
                // 設定容器的 data-v-app 屬性
                container.setAttribute('data-v-app', '');
            }
            
            // 傳回根元件的執行個體
            return proxy;
        };
        return app;
    }           

上面重寫的mount方法中,其實最主要的做的是三件事:

1.擷取挂載的容器

2.調用原本的mount方法挂載根元件

3.為容器設定vue的專屬屬性

現在到我們動手實作一個簡易版的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函數中的mount方法,我們可以看到mount方法的實作如下:

function mount(rootContainer, isHydrate, isSVG) {
        // 判斷是否已經挂載
        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.`);
            }
            // 堆代碼 duidaima.com
            // 通過在 createApp 中傳遞的參數來建立虛拟節點
            const vnode = createVNode(rootComponent, rootProps);
            
            // store app context on the root VNode.
            // this will be set on the root instance on initial mount.
            // 上面有注釋,在根節點上挂載 app 上下文,這個上下文會在挂載時設定到根執行個體上
            vnode.appContext = context;
            
            // HMR root reload
            // 熱更新
            if ((process.env.NODE_ENV !== 'production')) {
                context.reload = () => {
                    render(cloneVNode(vnode), rootContainer, isSVG);
                };
            }
            
            // 通過其他的方式挂載,這裡不一定指代的是服務端渲染,也可能是其他的方式
            // 這一塊可以通過建立渲染器的源碼可以看出,我們日常在用戶端渲染,不會使用到這一塊,這裡隻是做提示,不做具體的分析
            if (isHydrate && hydrate) {
                hydrate(vnode, rootContainer);
            }
            
            // 其他情況下,直接通過 render 函數挂載
            // render 函數在 createRenderer 中定義,傳遞到 createAppAPI 中,通過閉包緩存下來的
            else {
                render(vnode, rootContainer, isSVG);
            }
            
            // 挂載完成後,設定 isMounted 為 true
            isMounted = true;
            
            // 設定 app 執行個體的 _container 屬性,指向挂載的容器
            app._container = rootContainer;
            
            // 挂載的容器上挂載 app 執行個體,也就是說我們可以通過容器找到 app 執行個體
            rootContainer.__vue_app__ = app;
            
            // 非生産環境預設開啟 devtools,也可以通過全局配置來開啟或關閉
            // __VUE_PROD_DEVTOOLS__ 可以通過自己使用的建構工具來配置,這裡隻做提示
            if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
                app._instance = vnode.component;
                devtoolsInitApp(app, version);
            }
            
            // 傳回 app 執行個體,這裡不做具體的分析
            return getExposeProxy(vnode.component) || vnode.component.proxy;
        }
        
        // 如果已經挂載過則輸出提示消息,在非生産環境下
        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)``);
        }
    }           

通過上面的一通分析,其實挂載主要就是用的兩個函數将内容渲染到容器中;

createVNode 建立虛拟節點

render 渲染虛拟節點

我們這裡就實作一個簡易版的mount函數,來模拟挂載過程,代碼如下:

function mount(rootContainer, isHydrate) {
        // createApp 中傳遞的參數在我們這裡肯定是一個對象,是以這裡不做建立虛拟節點的操作,而是模拟一個虛拟節點
        const vnode = {
            type: rootComponent,
            children: [],
            component: null,
        }
        // 通過 render 函數渲染虛拟節點
        render(vnode, rootContainer);
        
        // 傳回 app 執行個體
        return vnode.component
    }           

虛拟節點

虛拟節點在Vue中已經是非常常見的概念了,其實就是一個js對象,包含了dom的一些屬性,比如tag、props、children等等。在Vue3中維護了一套自己的虛拟節點,大概資訊如下:

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的實作中也沒有那麼簡單,這一章不做具體的分析,隻是做一個簡單的概念介紹。

render

render函數是在講createRenderer的時候出現的,是在baseCreateRenderer中定義的,具體源碼如下:

function baseCreateRenderer(options, createHydrationFns) {
        // ...
        
        // 建立 render 函數
        const render = (vnode, container, isSVG) => {
            // 如果 vnode 不存在,并且容器是發生過渲染,那麼将執行解除安裝操作
            if (vnode == null) {
                // container._vnode 指向的是上一次渲染的 vnode,在這個函數的最後一行
                if (container._vnode) {
                    unmount(container._vnode, null, null, true);
                }
            }
            
            // 執行 patch 操作,這裡不做具體的分析,牽扯太大,後面會單獨講
            else {
                patch(container._vnode || null, vnode, container, null, null, null, isSVG);
            }
            
            // 重新整理任務隊列,通常指代的是各種回調函數,比如生命周期函數、watcher、nextTick 等等
            // 這裡不做具體的分析,後面會單獨講
            flushPreFlushCbs();
            flushPostFlushCbs();
            
            // 記錄 vnode,現在的 vnode 已經是上一次渲染的 vnode 了
            container._vnode = vnode;
        };
        
        // ...
        
        return {
            render,
            hydrate,
            createApp: createAppAPI(render, hydrate)
        };
    }           

render函數的主要作用就是将虛拟節點渲染到容器中,unmount函數用來解除安裝容器中的内容,patch函數用來更新容器中的内容,現在來實作一個簡易版的render函數:

const render = (vnode, container) => {
        
        patch(container._vnode || null, vnode, container);
        
        // 記錄 vnode,現在的 vnode 已經是上一次渲染的 vnode 了
        container._vnode = vnode;
    }           

unmount函數不是我們這次主要學習的内容,是以這裡不做具體的分析,patch函數是Vue中最核心的函數,這次也不做具體的分析,後面會單獨講,但是要驗證我們這次的學習成果,是以我們需要一個隻有挂載功能的patch函數,這裡我們就自己實作一個簡單的patch函數。

patch

patch函數的主要作用就是将虛拟節點渲染到容器中,patch函數也是在baseCreateRenderer中定義的。patch函數這次就不看了,因為内部的實作會牽扯到非常多的内容,這次隻是它的出現隻是走個過場,後面會單獨講。我們這次的目的隻是驗證我們這次源碼學習的成成果,是以我們隻需要一個隻有挂載功能的patch函數,這裡我們就自己實作一個簡單的patch函數;

// options 是在建立渲染器的時候傳入的,還記得在 createApp 的實作中,我們傳入了一個有 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.
    /**
     * 簡易版的實作,隻是删除了一些不必要的邏輯
     * @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) {
            // 如果是文本節點,那麼就直接建立文本節點,然後插入到容器中
            case Text:
                processText(n1, n2, container, anchor);
                break;
                
            // 還會有其他的類型,這裡不做具體的分析,後面會單獨講
                
            // 其他的情況也會有很多種情況,這裡統一當做是元件處理
            default:
                processComponent(n1, n2, container, anchor);
        }
    };           

patch函數的主要作用就是将虛拟節點正确的渲染到容器中,這裡我們隻實作了文本節點群組件的渲染,其他的類型的節點,後面會單獨講。而我們在使用createApp的時候,通常會傳入一個根元件,這個根元件就會走到processComponent函數中,是以我們這裡還需要實作了一個簡單的processComponent函數;

const processComponent = (n1, n2, container, anchor) => {
       if (n1 == null) {
           mountComponent(n2, container, anchor);
       }
       // else {
       //     updateComponent(n1, n2, optimized);
       // }
    };           

processComponent函數也是定義在baseCreateRenderer中的,這裡還是和patch函數一樣,隻是實作了一個簡單的功能,後面會單獨會講,processComponent函數做了兩件事,一個是挂載元件,一個是更新元件,這裡我們隻實作了挂載元件的功能。挂載元件是通過mountComponent函數實作的,這個函數也是定義在baseCreateRenderer中的,但是我們這次就不再繼續深入内部調用了,直接實作一個簡易的:

const mountComponent = (initialVNode, container, anchor) => {
        // 通過調用元件的 render 方法,擷取元件的 vnode
        const subTree = initialVNode.type.render.call(null);
        
        // 将元件的 vnode 渲染到容器中,直接調用 patch 函數
        patch(null, subTree, container, anchor);
    };           

這樣我們就實作了一個簡易版的挂載元件的功能,這裡我們隻是簡單的調用了元件的render方法,render方法會傳回一個vnode,然後調用patch函數将vnode渲染到容器中。現在回頭看看patch函數,還差一個processText函數沒有實作,這個函數也是定義在baseCreateRenderer中的,這個比較簡單,下面的代碼就是實作的processText函數:

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函數就是在我們實作簡易patch函數的時候,在patch函數實作的上面,通過解構指派擷取的,沒印象可以回去看看;

驗證

現在我們已經實作了一個簡易版的createApp函數,并且我們可以通過createApp函數建立一個應用,然後通過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…

總結

我們通過閱讀Vue3的源碼,了解了Vue3的createApp函數的實作,createApp函數是Vue3的入口函數,通過createApp函數我們可以建立一個應用。createApp的實作是借助了createRenderer函數,createRenderer的實作就是包裝了baseCreateRenderer,baseCreateRenderer函數是一個工廠函數,通過baseCreateRenderer函數我們可以建立一個渲染器。baseCreateRenderer函數接收一個options對象,這個options對象中包含了一些渲染器的配置,比如insert、createText等。

這些配置是在runtime-dom中實作的,runtime-dom中的createApp函數會将這些配置透傳遞給baseCreateRenderer函數,然後baseCreateRenderer函數會傳回一個渲染器,這個渲染器中有一個函數就是createApp。createApp函數接收一個元件,然後傳回一個應用,這個應用中有一個mount方法,這個mount方法就是用來将應用挂載到容器中的。

在createApp中重寫了mount方法,内部的實作是通過調用渲染器的mount方法,這個mount方法是在baseCreateRenderer函數中實作的,baseCreateRenderer函數中的mount方法會調用patch函數。patch函數内部會做很多的事情,雖然我們這裡隻實作了挂載的邏輯,但是也是粗窺了patch函數的内部一些邏輯。

最後我們實作了一個精簡版的createApp函數,通過這個函數我們可以建立一個應用,然後通過mount方法将應用挂載到容器中,這個過程中我們也了解了Vue3的一些實作細節。這次就到這裡,下次我們會繼續深入了解Vue3的源碼,希望大家能夠多多支援,謝謝大家!

文章來源:别再提起_https://www.duidaima.com/Group/Topic/Vue/9795

繼續閱讀