Vue源码速读 | 第一章:从 createApp 到 app:Vue.js 应用实例创建的内部工作原理

204 阅读2分钟

使用vue框架的第一步就是引入createApp和App组件,那么在源码中createApp到底是怎么创建App组件实例的呢?

一.创建App应用实例

1. 框架中的使用

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

const root = document.getElementById('root')
createApp(App).mount(root)
// App.js 本专栏均以这个app组件作为示例
export default {
  template: `<p>{{msg}}</p>`,
  setup() {
    return {
      msg: "Hello World",
    };
  },
};

2. createApp(源码)

源码createApp实际调用了ensureRenderer()函数返回值上的createApp方法

createApp() ---> ensureRenderer().createApp()

sequenceDiagram 
    createApp ->> createApp(App): 调用 
    createApp(App) ->> ensureRenderer().createApp(App): 返回
    createApp ->> ensureRenderer().createApp(App): 等于
const createApp = (...args) => {
    return ensureRenderer().createApp(...args);
};

3. ensureRenderer

源码定义了一个renderer作为渲染选项,同时ensureRenderer根据renderer是否存在判断是否通过createRenderer({})重新创建renderer。

sequenceDiagram 
    ensureRenderer().createApp(App) ->> ensureRenderer(): 调用
    ensureRenderer() ->> ensureRenderer().createApp(App):返回renderer || createRenderer()
//定义了全局的渲染选项
let renderer;
function ensureRenderer() {
    return (renderer ||
        (renderer = createRenderer({
            createElement,
            createText,
            setText,
            setElementText,
            patchProp,
            insert,
            remove,
        })));
}

4. createRenderer()

调用时createRenderer()时传入了框架已经封装好的对于DOM的操作函数(不需要刻意关注)。

// 以createElement为例
function createElement(type) {
    const element = document.createElement(type);
    return element;
}

createRenderer使用解构赋值读取了传入的dom操作函数并进行重命名,之后返回了一个对象包括了一个render函数和createApp函数。

所以此时我们调用的createApp函数即为这里的createAppAPI(render)的返回值

sequenceDiagram 
    ensureRenderer().createApp(App) ->> ensureRenderer(): 调用
    ensureRenderer() ->> createRenderer():调用
    createRenderer() ->> createAppApi():调用
    createRenderer() ->> ensureRenderer():返回 
    ensureRenderer() ->> ensureRenderer().createApp(App):返回{ renderer,createApp:createApi() }
    ensureRenderer().createApp(App) ->> createAppApi():调用
function createRenderer(options) {
    const { createElement: hostCreateElement, setElementText: hostSetElementText, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setText: hostSetText, createText: hostCreateText, } = options;
    const render = (vnode, container) => {
        patch(null, vnode, container);
    };
    return {
        render,
        createApp: createAppAPI(render),
    };
}

5. createAppAPI

createAppAPI返回函数createApp(rootComponent),函数内部返回一个名为app的对象,对象包括一个_component内部属性指向调用createApp传入的rootComponent参数,在这里指向我们使用main.js创建app实例时传入的App组件,同时对象还包括一个mount方法,mount方法需要一个参数rootContainer指向我们调用mounted传入的名为root的Dom元素。同时mount内部调用了createVNode和传入的render --> patch()。

createApp() --> ensureRenderer().createApp() --> renderer.createApp() --> createRenderer().createApp() --> createAppAPI(render)() --> app

sequenceDiagram 
    ensureRenderer().createApp(App) ->> createAppApi() : ensureRenderer()等于 { renderer,createApp:createApi() }
    createAppApi() ->> ensureRenderer().createApp(App):返回createApp函数
function createAppAPI(render) {
    return function createApp(rootComponent) {
        const app = {
            _component: rootComponent,
            mount(rootContainer) {
                const vnode = createVNode(rootComponent);
                render(vnode, rootContainer);
            },
        };
        return app;
    };
}

6. 总结

从createApp(App) --> 到最后实际调用了createAppAPI返回的createApp(rootComponent),

借用了JS的闭包(createApp 可以访问其外部作用域中的 render 函数,即使 createAppAPI 函数已经返回并且其外部作用域已经结束,也可以使用 render 函数),最后我们创建的应用实例实际为一个app对象,对象内部包含了一个内部属性_component指向传入的App组件,一个mount挂载函数,挂载函数内部调用了我们在createApp过程中传入的render函数(实际为patch函数)。同时全局属性renderer被赋值为一个包括render属性和createApp属性的对象。

sequenceDiagram 
    createApp(App) ->> ensureRenderer().createApp(App): 调用
    ensureRenderer().createApp(App) ->> renderer.createApp(App): 调用
    renderer.createApp(App) ->> createRenderer().createApp(App): 调用
    createRenderer().createApp(App) ->> createAppAPI(render)(App): 调用
    createAppAPI(render)(App) ->> app: 返回
    createApp(App) ->> app:得到
const app = {
    _component: rootComponent, //传入的App组件
    mount(rootContainer){
        const vnode = createVNode(rootComponent);
        render(vnode, rootContainer);//patch()
    }    
}
let renderer;
renderer = {
    render, //patch()
    createApp:createAppAPI(render)
}