Vue3 源码:createApp 与 mount 挂载实现流程
从一行 createApp 说起
每个 Vue 3 项目的入口都是这几行:
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
这行代码把两个东西连了起来:App.vue 这个组件,和 index.html 里那个 <div id="app">。中间发生的事情可以概括成一句话——把组件转成虚拟节点,再把虚拟节点渲染成真实 DOM。
createApp 本身不做渲染,它返回一个 app 实例对象。这个对象上除了 mount,还挂着 use、component、directive、mixin 这些方法,所以我们能写成链式调用 createApp(App).use(router).use(store).mount('#app')。这些方法都在往一个应用级的上下文里登记东西,等到 mount 时一并生效。这也是 Vue 3 相比 Vue 2 的一个改进:插件和全局组件注册在 app 实例上,而不是挂到全局的 Vue 构造函数上,多个应用实例之间互不污染。
下面这份实现把非核心的部分都剥掉了,只留最短的那条链路。
createApp
在 createApp 中我们接受 rootComponent(App.vue),创建一个 mount 挂载方法,参数 rootContainer 为 index.html 中的根节点。
export function createApp(rootComponent) {
return {
mount(rootContainer) {
console.log('基于根组件创建 vnode');
// 先将 App.vue 这个组件转换为虚拟节点,后续的所有操作都是在虚拟节点上进行操作
const vnode = createVNode(rootComponent);
console.log('调用 render,基于 vnode 进行开箱');
render(vnode, rootContainer);
},
};
}
- 在
mount方法中,我们使用一个createVNode方法将rootComponent(App.vue)转换为虚拟节点 - 然后通过再通过一个 render 函数将转换好的虚拟节点的内容渲染到
rootContainer节点下面
为什么要先转成虚拟节点
createVNode 返回的是一个普通 js 对象,描述「要渲染什么」:type 指向组件对象或标签名,props 是属性,children 是子节点,另外还有 shapeFlag 这类标记位,用来快速判断这个节点是组件还是元素、children 是数组还是文本。
多这一层的好处有两个。一是把「描述」和「操作 DOM」分开了,后续所有 diff、更新、复用判断都在这些轻量对象上进行,只在最后必要时才碰真实 DOM。二是描述层不绑定平台,同一套 vnode 可以交给不同的渲染器去处理,这也是 Vue 3 能同时支持浏览器 DOM、自定义渲染器(比如渲染到 canvas 或原生平台)的基础。
完整链路里被省略的部分
真实源码比上面这份多一层。createApp 不是直接定义的,而是这么来的:ensureRenderer() 拿到一个针对浏览器 DOM 的渲染器(内部通过 createRenderer 传入一组 DOM 操作方法,比如 insert、remove、createElement、patchProp),渲染器上带着 createApp 方法,这个方法由 createAppAPI(render) 生成。
ensureRenderer 的名字说明了它是懒创建的:只在第一次调用时创建渲染器并缓存下来。这样如果项目里只用响应式 API 而不做渲染,这部分代码能被 tree-shaking 掉。
createAppAPI 是个高阶函数,它把 render 闭包进去,返回真正的 createApp。这层设计的意义在于 render 是平台相关的,而 createApp、mount、use 这些逻辑是平台无关的,拆开之后写自定义渲染器只需要提供一组节点操作方法。
mount 内部还多做了两件我们省略掉的事:把传进来的 '#app' 字符串通过选择器查成真实元素,以及在渲染完成后把 vnode 挂到容器的 _vnode 属性上。第二件事是为了后续更新——下一次对同一个容器调用 render 时,能取出上一次的 vnode 做对比走 patch 流程,而不是整个重建。
所以整条链路是:createApp(App) → ensureRenderer() → createAppAPI(render) → mount('#app') → createVNode(App) → render(vnode, container),之后交给 patch 按 vnode 类型分发处理。组件类型的节点会走 mountComponent,在那里创建组件实例、执行 setup、建立渲染 effect,把组件的渲染函数和响应式系统连起来——这样数据变化才能自动触发重新渲染。

还没有留言,来发表第一条吧~