跳到主要内容

createApp 流程

创建 vue3 项目时,在main.js里面一般会有如下代码:

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

createApp(App).mount('#app')

我们来看看createApp(App)这个流程做了什么.

首先利用 vscode 创建一个调试配置文件,配置如下:

{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "针对 localhost 启动 Chrome",
"url": "http://localhost:5173/",
"webRoot": "${workspaceFolder}"
}
]
}

url 是本地的项目运行地址.创建好后,打上断点,点击绿色小箭头就可以开始调试了. 点击进入当前函数,就会来到 createApp 定义的地方. 它的源码在core/packages/runtime-dom/src/index.ts这个文件里面.可以直接在源码文件里面查看这个方法的调用. 可以看到实际上是调用了ensureRenderer里面的createApp方法,并且重写了 mount 挂载方法.

继续搜索到ensureRenderer方法,发现它是通过 createRenderer 方法创建的.

function ensureRenderer() {
return (
renderer ||
((renderer = createRenderer < Node),
Element | (ShadowRoot > rendererOptions))
)
}

createRenderer 这个方法在core/packages/runtime-core/src/renderer.ts里面

export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer < HostNode, HostElement > options
}

可以看到实际上还是调用了 baseCreateRenderer 方法.这个方法就在此文件里面,直接搜到会发现它有 2 千多行!.我们现在先不用关心这个函数做了什么,直接看函数的返回. 可以看到返回的 createApp 函数就是 createAppAPI 函数. 经过上面几层函数的来回调用,到现在就找到了 createApp 这个函数最终定义的地方了,利用断点,会比我们在原文件里面更方便的找到这里.

看一下返回 app 对象里面的属性 可以看许多熟悉的 api, use、mixin、component、directive、mount、unmount、provide . 这就是对应的 app 实例上面的方法.

mount 挂载

看了 app 的实例,接着我们在看下 mount 挂载方法. 虽然这里对 mount 方法进行重写,但是最终还是调用的是 app 里面的 mount 方法(104 行).可以在这里打上断点,发现执行完这一步后页面就会渲染出来.继续回到 createAppAPI,找到 mount 方法. 可以看到这里面主要就是利用传进来 rootComponent (根组件就是上面的 App 组件)创建 vnode(虚拟节点)后调用 render (进行渲染)函数.其余的边界判断先不做分析.

vnode​

core/packages/runtime-core/src/vnode.ts里面 createBaseVNode 函数定义了一个 vnode 对象. 最后将这个 vnode 返回,可见 vnode 是一个对象.

render 方法​

这里看一下 render 方法,他是在 baseCreateRenderer 函数里面定义的,作为参数传给 createApp 的

const render: RootRenderFunction = (vnode, container, namespace) => {
// 这里先进行了判断,如果没有 vnode 并且已经渲染过vnode,就卸载掉当前的 vnode
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(
container._vnode || null,
vnode,
container,
null,
null,
null,
namespace
)
}
// 更新的队列(vue是异步更新dom的)
if (!isFlushing) {
isFlushing = true
flushPreFlushCbs()
flushPostFlushCbs()
isFlushing = false
}
// 保存当前的 vnode
container._vnode = vnode
}

patch 方法是更新节点的方法,里面包含了 diff 算法,可以看这篇文章 最后将当前任务推进任务队列去进行执行.

通过这函数的最后一段代码可以看到 vue 将 vnode 挂载到容器(也就是它的真实 dom 元素)上.这样方便后面进行新旧节点的比较.我们打上断点在浏览器里面就可以看到.

总结

createApp 是由最终调用的 createAppAPI 方法(core/packages/runtime-core/src/apiCreateApp.ts)返回,createApp 里面 将 use,directive,mount,provide 等指令挂载在 app 实例上面.

一般实例创建完成后都会调用 mount 方法.mount 方法里面先创建 vnode 后调用 render 里面 patch 函数进行更新渲染.