動態

詳情 返回 返回

【源碼&庫】在調用 createApp 時,Vue 為我們做了那些工作? - 動態 詳情

在使用Vue3時,我們需要使用createApp來創建一個應用實例,然後使用mount方法將應用掛載到某個DOM節點上。

那麼在調用createApp時,Vue再背後做了些什麼事情呢?今天就來扒一扒Vue3的源碼,看看調用createApp發生了些什麼。

大家好,這裏是田八的【源碼&庫】系列,Vue3的源碼閲讀計劃,Vue3的源碼閲讀計劃不出意外每週一更,歡迎大家關注。

首發在掘金,如果想一起交流的話,可以點擊這裏一起共同交流成長

系列章節:

  • 【源碼&庫】跟着 Vue3 學習前端模塊化

尋找入口

在上一章中,我們我們已經將Vue3的源碼下載下來了,並且已經知道如何編譯源碼了,先看一下Vue3的源碼目錄:

image.png

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文件,這個文件是Vue3ESM版本,會通過import來引入其他模塊,這樣就可以直接看到Vue3的模塊劃分。

本系列就會通過vue.esm-bundler.js文件來分析Vue3的源碼,並且會通過邊分析邊動手的方式來學習Vue3的源碼。

image.png

使用

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

import {createApp} from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

Vue3中,我們需要使用createApp來創建一個應用實例,然後使用mount方法將應用掛載到某個DOM節點上。

createApp是從vue包中導出的一個方法,它接收一個組件作為參數,然後返回一個應用實例。

入口 createApp

vuepackage.json可以看到,module字段指向了dist/vue.esm-bundler.js文件,這個文件是Vue3ESM版本,我們可以直接使用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';

// ... 省略n多代碼

function createApp(...args) {
    // ...
}

export {createApp};

可以看到runtime-dom包中還引用了runtime-core包和shared包,現在找到入口文件了,在分析直接可以先搭建一個簡單的代碼分析和測試的環境,這樣方便自己驗證並且可以直接看到代碼的執行結果。

demo環境可以直接在本地搭建,也可以使用codesandboxstackblitz等在線環境,這裏使用codesandbox,後續demo的代碼都會放在codesandbox上,文末會有鏈接。

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

源碼分析

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

createApp

const createApp = (...args) => {
    const app = ensureRenderer().createApp(...args);

    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方法的執行結果,這個方法的作用會返回renderhydratecreateApp三個方法;

而我們最後需要調用的createApp方法就是在這三個方法中的其中一個,而createApp方法的是通過createAppAPI方法創建的,同時剩下的兩個方法renderhydrate也是在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 就是我們傳入的根組件,這裏會做一些校驗
        
        // 如果傳遞的不是一個函數,那麼就做一個淺拷貝
        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方法時,可以調用的方法;

這裏可以看到我們常用的usemixincomponentdirectivemountunmountprovide等方法都是在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.`);
        }
        
        // 通過在 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)``);
    }
}

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

  1. createVNode 創建虛擬節點
  2. 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的一些屬性,比如tagpropschildren等等;

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

我這裏屏蔽掉了更新的操作,這裏只管掛載,這裏的hostInserthostCreateText函數就是在我們實現簡易patch函數的時候,在patch函數實現的上面,通過解構賦值獲取的,沒印象可以回去看看;

驗證

現在我們已經實現了一個簡易版的createApp函數,並且我們可以通過createApp函數創建一個應用,然後通過mount方法將應用掛載到容器中;

我們可以通過下面的代碼來驗證一下:

import { createApp } from "./runtime-dom";

const app = createApp({
  render() {
    return {
      type: "Text",
      children: "hello world"
    };
  }
});

app.mount("#app");

源碼在codesandbox上面,可以直接查看:https://codesandbox.io/s/gallant-sun-khjot0?file=/src/main.js

總結

我們通過閲讀Vue3的源碼,瞭解了Vue3createApp函數的實現,createApp函數是Vue3的入口函數,通過createApp函數我們可以創建一個應用;

createApp的實現是藉助了createRenderer函數,createRenderer的實現就是包裝了baseCreateRenderer

baseCreateRenderer函數是一個工廠函數,通過baseCreateRenderer函數我們可以創建一個渲染器;

baseCreateRenderer函數接收一個options對象,這個options對象中包含了一些渲染器的配置,比如insertcreateText等;

這些配置是在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的源碼,希望大家能夠多多支持,謝謝大家!

user avatar honwhy 頭像 guochenglong 頭像 lazytimes 頭像 yelloxing 頭像 6fafa 頭像 werbenhu 頭像 enjolras1205 頭像 aresn 頭像 rk405264704 頭像 nihaoanihao 頭像 huaiyug 頭像 yazi_6005853a842a6 頭像
點贊 28 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.