实现 createApp、mount 和 unmount
2025-10-27 12:41:48
前面已经实现了 render。
但 render 是比较底层的 API,使用时需要先手动创建 vnode,再把它渲染到容器中:
const vnode = h('div', 'hello')
render(vnode, document.querySelector('#app'))
但平时写 vue 代码,常见的是这样:
import { createApp, h } from '../dist/vue.esm.js'
const App = {
render() {
return h('div', 'hello')
}
}
createApp(App, { msg: 'hello' }).mount('#app')
所以在 render 的基础上补上 createApp。
可以看出,createApp 的核心就是将组件挂载到 DOM 上,这和 render 的职责类似。
render 负责将虚拟节点渲染到容器中,因此只需要先把组件转换成虚拟节点,再交给 render 处理:
createApp(App, props).mount(container)
//
const vnode = h(App, props)
render(vnode, container)
所以,createApp 本质上只是对 render 的一层应用级封装。
基础签名
function createRenderer(options) {
const render = (vnode, container) => {
}
return {
render,
createApp(rootComponent, rootProps) {
function mount(container) {
}
function unmount() {
}
return {
mount,
unmount
}
}
}
}
将代码拆分一下,把 createApp 抽离到 apiCreateApp.ts 中:
import { createAppAPI } from './apiCreateApp'
export function createRenderer(options) {
const render = (vnode, container) => {
// ...
}
return {
render,
createApp: createAppAPI(render)
}
}
renderer.ts 主要负责渲染流程,createApp 则负责对外暴露应用入口。
createAppAPI
// packages/runtime-core/src/apiCreateApp.ts
import { h } from './h'
export function createAppAPI(render) {
return function createApp(rootComponent, rootProps) {
const app = {
_container: null,
mount(container) {
const vnode = h(rootComponent, rootProps)
render(vnode, container)
app._container = container
},
unmount() {
if (app._container) {
render(null, app._container)
}
}
}
return app
}
}
选择器支持
现在 runtime-core 里的 mount 接收的是容器节点,但平时使用 vue 时,也可以直接传入选择器:
createApp(App).mount('#app')
runtime-core 是平台无关的,选择器属于浏览器平台能力,所以应该放到 runtime-dom 中处理。
runtime-dom 包装 mount
在 runtime-dom 中,可以先拿到 runtime-core 创建出来的 app,再重写它的 mount 方法:
// packages/runtime-dom/src/index.ts
import { createRenderer } from '@vue/runtime-core'
import { isString } from '@vue/shared'
import { nodeOps } from './nodeOps'
import { patchProp } from './patchProp'
export * from '@vue/runtime-core'
const rendererOptions = {
patchProp,
...nodeOps
}
const renderer = createRenderer(rendererOptions)
export function render(vnode, container) {
renderer.render(vnode, container)
}
export function createApp(rootComponent, rootProps = null) {
const app = renderer.createApp(rootComponent, rootProps)
const mount = app.mount.bind(app)
app.mount = function (containerOrSelector) {
let container = containerOrSelector
if (isString(containerOrSelector)) {
container = document.querySelector(containerOrSelector)
}
mount(container)
}
return app
}
这样传入 '#app' 时,会先把它转换成真实 DOM 节点,之后再调用原来的 mount,把根组件渲染进去。
这样,createApp 的基础流程就打通了:
小结
runtime-dom处理选择器,拿到真实容器runtime-core创建根组件vnode- 调用
render(vnode, container)挂载应用 - 调用
render(null, container)卸载应用