Appearance
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函数的核心功能,只不过我们在这里做了一些封装而已。