Skip to content

createApp的使用

看一下createApp的使用:

ts
import { h, ref, createApp } from 'vue'
// 创建一个组件
const Comp = {
    render() {
        return h('div', 'hello world')
    }
}
// 创建一个应用实例
const app = createApp(Comp)
// 将组件挂载到id为app额dom元素上
app.mount('#app')

createAppAPI的核心功能,是Vue应用初始化的关键入口,createApp主要作用就是将一个组件,挂载到一个DOM节点上。很容易想到render函数,render函数的主要作用是将一个虚拟节点渲染到某一个容器中。那么如果这样,是不是可以直接将这个组件转换成一个虚拟节点,然后调用render函数。实际上,createApp的实现就是这样的。

核心实现

其实createApp这个函数,也是由createRenderer这个函数返回的:

ts
function createRenderer(options) {
    // 省略部分代码。。。
    const render = (vnode, container) {

    }
    return {
        render,
        // 在这里返回一个createApp函数
        createApp: createAppAPI(render)
    }
}

createRenderer函数中,返回了一个createApp函数,这个函数是由createAppAPI创建出来的,接下来完成createAppAPI函数的实现.

1.createAppAPI的实现

packages/runtime-core/src/apiCreateApp.ts中,创建一个createAppAPI函数:

ts
export function createAppAPI(render) {
    // 返回了createApp函数
    return function createApp(rootComponent, rootProps) {
        // 创建一个应用实例
        const app = {
            _container: null,
            mount(container) {
                // mount方法会接受一个container,是一个dom元素,也必须是一个dom元素
                // 在mount方法中,我们使用h函数将组件转换成虚拟节点
                const vnode = h(rootComponent, rootProps)
                // 调用rener函数将虚拟节点渲染到容器中
                render(vnode, container)
                // 将容器保存到实例中
                app._container = container
            },
            unmount() {
                // 卸载组件,卸载就是将虚拟节点渲染成 null
                render(null, app._container)
            }
        }
        return app
    }
}

这里实现了一个createApp函数,这个函数会返回一个应用实例,这个应用实例有两个方法,一个是mount方法,一个是unmount方法,mount方法会接受一个DOM元素,也就是我们要挂载的容器,在mount方法中,我们使用h函数将组件转换成虚拟节点,然后调用render函数将虚拟节点渲染到容器中,最后将容器保存到应用实例中。

2.支持选择器字符串

在使用mount方法的时候,必须传递一个DOM元素,如果传递一个选择器字符串的话,就会报错,这个时候需要对mount方法进行扩展,在packages/runtime-dom/src/index.ts中,实现这个功能:

之所以不在runtime-core中实现这个功能,是因为在runtime-core中不能操作DOM元素,只能借助runtime-dom来实现功能。

ts
// packages/runtime-dom/src/index.ts
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
import { createRenderer } from '@vue/runtime-core'
import { isString } from '@vue/shared'

export * from '@vue/runtime-core'

const renderOptions = { patchProp, ...nodeOps }

const renderer = createRenderer(renderOptions)

// 创建一个createApp函数,内部调用renderer.createApp
export function createApp(rootComponent, rootProps) {
    // 先创建一个应用实例
    const app = renderer.createApp(rootComponent, rootProps)
    // 保存原始的mount方法
    const _mount = app.mount.bind(app)

    // 重写mount方法
    function mount(selector) {
        // 默认传入的selector是一个dom元素
        let el = selector
        if(isString(selector)) {
            // 如果传入的是字符串,则使用querySelector获取dom元素
            el = document.querySelector(selector)
        }
        _mount(el)
    }
    // 将重写的mount方法赋值给应用实例
    app.mount = mount

    return app
}

这样createApp就支持传递字符串作为选择器了

总结

至此完成了createApp的核心功能的实现,实际上createApp只是一个简单的函数,它的主要作用就是将一个组件挂载到一个DOM节点上,这个过程其实就是将组件转换成虚拟节点,然后调用render函数将虚拟节点渲染到容器中,这个过程其实就是我们之前一直在用的render函数的核心功能,只不过我们在这里做了一些封装而已。

Released under the MIT License.