前言:
在源码分析概述中, 我们对源码源码结构进行了分析, 整个vue三大核心系统, 即响应式系统, 渲染系统,编译系统. 而响应式系统和渲染系统 又共同构建了vue官网上所谓的运行时构建
vue3 源码中的三大系统有明确的分界, 但在使用时, 通常并不会单独区分使用某一个系统, vue3的应用是在三个系统相互作用的结果, 至少是运行时, 即包括渲染系统 和响应式系统. 所以我的对vue3源码分析会根据需要交替分析三大系统中对应的源码
在vue3中, 我们需要使用createApp API 来创建应用实例, 然后通过创建的实例调用mount方法将应用挂载到DOM 节点上, 因此createApp可以理解为整个vue3 应用的入口, 包括官网API都将createApp排列在第一位
所以今天这篇文章, 主要针对createApp创建vue3应用的API 进行源码分析.
在分析之前, 我先放一张createApp初始化应用的流程图: createApp初始化流程图如下:

如果你此时看到该流程图是一个呆萌的状态, 可以在看完该文章后在回头来看一下该流程图, 你就会非常清楚的知道createApp 函数都做了什么
好, 接下来我们开始正式分析createApp创建应用API的具体实现, 那接下请大家跟着我一起探寻一下createApp API 源码内部逻辑, 看看源码中都做了些什么.
1. 初始化应用
我们使用一个具体的实例作为切入点, 通过断点调试的方式对createApp源码进行分析.理解vue3初始化的过程.
实例如下:
ts
const { createApp } = Vue
const app = createApp({
template: '<h1>hello</h1>'
})
app.mount('#app')
这是一个最简单的实例, 在实例中, 我们首先通过解构的方式获取createApp方法. 通过createApp方法创建应用, 并通过mount方法将应用挂载到DOM 上
接下来我们将通过这个实例分析createApp创建vue3应用的API , 以及mount挂载方法.
2. 创建vue3应用的源码分析
2.1 定位 createApp 方法
我们知道vue3 源码采用pnpm多包管理, 目前我们只知道createAppAPI 是从vue中结构出来的, 那么该API具体来自于哪个包中, 现在并不知道.
因此在分析createAppAPI 前, 我们需要先确认该API 来自于哪个包.
我们可以通过断点调试的分时来分析, 使用单步调试, 进入到createApp方法内
此时就可以看到createApp方法来自于packages/runtime-dom包中
runtime是运行时.其中包括runtime-dom,runtime-core两个包
runtime-dom处理与浏览器相关的dom 操作的APIruntime-core是运行时核心代码,与渲染平台无关
也就意味着runtime-dom的运行依赖于runtime-core包, 所以在runtime-dom/src/index.ts模块中一定会引入runtime-core包
ts
import { /.../ } from '@vue/runtime-core'
// 创建 vue 应用API
export const createApp = ((...args) => {
// ...
}) as CreateAppFunction<Element>
2.2 createApp 函数分析
在确定createApp 函数后, 接下来就是分析该方法的实现 源码:
ts
export const createApp = ((...args) => {
// 1. 首先创建应用
const app = ensureRenderer().createApp(...args)
// 2. 重写mount 方法
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// ...后面分析
}
//3.返回应用
return app
}) as CreateAppFunction<Element>
这里我对源码进行了精简, 这样非常方便的可以看出.createApp函数的核心逻辑就是创建Vue3应用对象app并返回, 至于重写mount方法, 稍后我会带大家具体分析
我们先看下createApp的入参, 用过createAppAPI的朋友, 相信大家都知道, createApp接收两个入参
根组件: 根组件可以是单文件组件, 也可以是组件的选项对象props: 向根组件传入的props数据, 该参数为可选参数.
在源码中可以看出, createApp函数通过...args剩余运算符收集所有入参, 将参数组合成为数组. 如果传入了第二个参数props, args数组收集到的数据将会有两项, 否则将只有一项, 即根组件对象.
所以createApp函数会接收根组件作为参数, 并且返回vue应用, 即方法内app就是createApp创建的应用对象
我相信大家已经看出来了, createApp函数中创建的应用对象app其本质是通过ensureRenderer().createApp(...args)创建的
通过这个结构不难看出: ensureRenderer()方法调用完毕后返回一个包含createApp方法的对象, 并通过该方法的调用创建vue应用
2.3 ensureRenderer 确认渲染器
接下来我们需要先确认ensureRenderer确认渲染器函数 源码:
ts
// Renderer 为Web前端渲染器 , HydrationRenderer为SSR服务器端渲染使用,
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer
function ensureRenderer() {
return (
renderer ||
(renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
)
}
通过ensureRenderer 函数内部逻辑不难看出,ensureRenderer函数的作用就是用来确定渲染器对象(renderer) 存在.
如果渲染器renderer不存在, 就会调用createRenderer函数创建渲染器, 根据前面的分析, 渲染器将是一个包含createApp方法的对象
初始创建vue应用renderer值为必然为空, 会调用createRenderer函数, 并传入参数rendererOptions 这里rendererOptions参数是渲染器配置对象, 主要作用就是用来操作DOM的一些方法
源码:
ts
// 渲染器配置对象
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
// extend 就是合并对象的方法(shared/src/general.ts)
export const extend = Object.assign
// dom 操作(runtime-core/src/nodeOps.ts )
const doc = (typeof document !== 'undefined' ? document : null) as Document
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
// 插入节点
insert: (child, parent, anchor) => {
parent.insertBefore(child, anchor || null)
},
// 创建文本节点
createText: text => doc.createTextNode(text),
// 创建注释节点
createComment: text => doc.createComment(text),
//...
}
这里将几个模块中的中的方法放在了一起, 很方便的看出, rendererOptions渲染器配置对象就是封装了DOM操作方法的对象, 由两部分组成:
patchProp:处理元素的props、Attribute、class、style、event事件等。nodeOps:处理DOM节点,这个对象里面包含了各种封装原生DOM操作的方法。
rendererOptions对象最终会传递到渲染器里面,里面的各种方法最终会在页面渲染的过程中被调用。
至此我们暂时了解如下几件事:
createApp方法的作用就是创建Vue3应用.createApp方法内真正创建Vue3应用的是渲染器对象的createApp方法createRenderer方法的作用是用来创建渲染器对象, 接收rendererOptions渲染器配置对象rendererOptions配置对象中包含DOM操作的方法
因此要创建Vue3 应用必须先创建渲染器对象, 因此接下来我们继续分析createRenderer方法的实现
2.4 createRenderer 创建渲染器方法
createRenderer方法来源于runtime-core/src/render.ts模块 具体源码实现如下
ts
// 创建渲染器方法
function createRenderer(options) {
return baseCreateRenderer(options)
}
// 真正创建渲染器函数
function baseCreateRenderer(options,createHydrationFns) {
// ... 省略渲染方法
// 渲染函数
const render = (vnode, container, isSVG) => {
//...
}
let hydrated;
let hydrateNode;
if (createHydrationFns) {
;[hydrate, hydrateNode] = createHydrationFns(internals)
}
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
通过源码可以看出createRenderer函数返回baseCreateRenderer方法执行的结果, 即真正创建渲染器对象的函数是baseCreateRenderer baseCreateRenderer创建的渲染器对象包括render, hydrate, createApp三个方法
获取到渲染器对象后, 就会通过渲染器对象调用createApp方法创建应用. 而createApp方法的值是通过createAppAPI方法创建的, 同时会将render, hydrate,两个方法作为参数传入createAppAPI方法中.
所以接下来我们需要看一下createAppAPI方法的实现
2.5 createApp API 方法
createAppAPI方法来自于runtime-core/src/apiCreateApp.ts
具体实现如下
ts
let uid = 0
export function createAppAPI(render,hydrate){
// 返回创建应用的createApp 方法(利用闭包缓存render, hydrate 参数)
return function createApp(rootComponent, rootProps = null) {
// rootComponent 就是传入的根组件
// rootProps 为向根组件传入props 数据
// 1. 如果根组件不是函数, 则进行一次浅拷贝
if (!isFunction(rootComponent)) {
rootComponent = extend({}, rootComponent)
}
// 2.rootProps 为传入根组件的props , 参数必须是一个对象或null
if (rootProps != null && !isObject(rootProps)) {
__DEV__ && warn(`root props passed to app.mount() must be an object.`)
rootProps = null
}
// 3. 创建vue应用实例上下文对象, 就是一些默认配置
const context = createAppContext()
// 4. 初始化插件集合, 记录通过use 创建的插件
const installedPlugins = new WeakSet()
// 5. 始化应用挂载状态,默认为false
let isMounted = false
// 6. 创建 vue 应用实例对象
// 同时将应用实例存储到context上下文的app属性
const app: App = (context.app = {
// 初始化应用属性
_uid: uid++, // 项目中可能存在多个vue实例,需使用id标识
_component: rootComponent as ConcreteComponent, // 根组件
_props: rootProps, // 传递给根组件的props
_container: null, // 挂载点: DOM容器
_context: context, // app上下文
_instance: null, // 应用实例
version, // 版本
// 定义了一个访问器属性app.config,只能读取,不能直接替换
get config() {return context.config},
set config(v) {},
// 挂载插件, 返回app对象
use(plugin, ...options) {/*... 省略代码*/ return app },
// 混入
mixin(mixin) { /*... 省略代码*/ return app },
// 注册全局组件
component(name, component) { /*... 省略代码*/ return app },
// 定义全局指令
directive(name, directive) { /*... 省略代码*/ return app },
// 应用挂载/卸载方法
mount( rootContainer,isHydrate,isSVG ) {/*... 省略代码*/},
unmount() { /*... 省略代码*/ },
// 全局注入方法
provide(key, value) {/*... 省略代码*/ return app },
runWithContext(fn) {/*... 省略代码*/ }
})
// __COMPAT__ 判断是否兼容vue2
// 若开启兼容,安装vue2相关的API
if (__COMPAT__) {
installAppCompatProperties(app, context, render)
}
// 返回 app 对象
return app
}
}
源码中createAppAPI函数返回了一个函数, 返回的这个函数才是真正的createApp函数.
因此该返回的函数就是最后用来创建应用对象的createApp方法
分析至此,我们就可以知道了
createAppAPI 创建应用实例对象并返回createAppAPI 中创建应用对象, 是通过调用渲染器{render,hydrate,createApp}中的createApp方法创建的- 渲染器中的
createApp方法则是createAppAPI函数的返回函数
而createApp创建的应用对象就是包含了一些属性和常用的use,mixins,component,directive,mount,unmount,provide方法的对象. 因此可以通过app应用对象调用mount挂载应用
3. mount 挂载源码分析
在上面我们已经分析了createApp函数, 在调用完毕后返回vue应用实例对象, 在使用时会通过应用实例对象调用mount方法挂载应用.
3.1 确定mount 方法
通过前面的分析,我们已经明白, 创建应用实例的createApp函数是通过调用createApp API方法返回的createApp 创建的应用实例. 在此方法中可以看到生成应用对象app具有mount方法
源码:
ts
export function createAppAPI(render,hydrate){
// 创建vue应用实例的方法
return function createApp(rootComponent, rootProps = null) {
//...
// 应用实例对象
const app: App = (context.app = {
//..
// 应用实例对象挂载mount 方法
mount(rootContainer,isHydrate,isSVG) {
// ...
},
//...
})
//..
// 返回应用实例对象
return app
}
}
但我们调用app.mount()挂载应用时并不是调用该mount 方法, 原因在于createApp方法中对当前mount方法进行了重写
ts
export const createApp = ((...args) => {
// 1. 首先创建应用
const app = ensureRenderer().createApp(...args)
// 2. 重写mount 方法
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
// ...后面分析
}
//3.返回应用
return app
}) as CreateAppFunction<Element>
源码很清楚的表明, createApp函数中创建完应用实例对象, 接着通过解构的方式将应用实例对象app的mount方法记录在变量mount上
然后重写app对象的mount方法进行重写, 赋值了一个新的方法
也就是在在我们通过app.mount()挂载应用时, 进入的是createApp函数中对app应用对象重写的mount方法
3.2 mount 挂载
在实例中,我们调用mount方法进行挂载时,传入了一个字符串#app , 以此获取到挂载点DOM元素
ts
app.mount('#app')
mount具体是如何挂载的呢?, 我们首先分析一下createApp函数中重写的mount方法
重写的mount方法
ts
export const createApp = ((...args) => {
// 创建应用实例对象
const app = ensureRenderer().createApp(...args)
// 重写mount 方法
// 备份mount 方法
const { mount } = app
// 重写mount 方法
app.mount = (containerOrSelector: Element | ShadowRoot | string) => {
// 1. 获取挂载容器(dom元素)
const container = normalizeContainer(containerOrSelector)
// 没有容器直接return 无法挂载应用
if (!container) return
// 2. 处理根组件
// 通过应用对象获取根组件, 即createApp() 方法第一个参数
const component = app._component
// 验证根组件是一个对象,且不具有render, template属性, 则使用挂载点内容作为模板
if (!isFunction(component) && !component.render && !component.template) {
// __UNSAFE__
// Reason: potential execution of JS expressions in in-DOM template.
// The user must make sure the in-DOM template is trusted. If it's
// rendered by the server, the template should not contain any user data.
// 需要确保挂载点模板是可信的, 因为模板中可能存在JS表达式
component.template = container.innerHTML
}
// 清空挂载点
// clear content before mounting
container.innerHTML = ''
// 3. 调用 mount 真正的挂载
// 调用从应用实例上备份的mount 进行挂载
const proxy = mount(container, false, container instanceof SVGElement)
// 挂载完成后, 清理v-clock指令, 为容器添加data-v-app 标识
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
// 返回根组件实例对象(注意不是app应用实例对象)
return proxy
}
// 返回应用对象
return app
}) as CreateAppFunction<Element>
可以看到重写的mount方法通过调用normalizeContainer获取挂载点容器, 其具体实现如下:
源码:
ts
// 获取挂载点dom 容器
function normalizeContainer(
container: Element | ShadowRoot | string
): Element | null {
// 1. 参数为字符串, 则通过querySelector 获取dom 元素
if (isString(container)) {
const res = document.querySelector(container)
return res
}
// 2. 参数不是字符串, 则直接返回
return container as any
}
在调用mount方法挂载时, 参数可以是字符串, 也可以是真实的DOM元素.因此在该方法中针对参数进行判断,
- 如果参数是字符串, 则认为是选择器, 通过
querySelector获取DOM元素 - 如果参数不是字符串, 则认为参数是真实的DOM 元素, 直接返回
上面重写的mount方法, 其实最主要的就是做了三件事
- 获取挂载点容器, 通过
normalizeContainer方法获取 - 处理根组件对象, 根组件对象如果不具有渲染函数
render方法和模板template, 则使用挂载点元素内容作为template模板 - 调用应用对象
app原本的mount方法挂载根组件, 挂载完成后为容器添加特定的属性
3.3 应用对象本身的mount 方法
在重写mount方法中最终会调用应用对象app本身的mount方法进行正式的挂载,
mount方法接收三个参数
- 挂载点容器:
container - 是否为
SSR: 传入固定值false - 是否为
SVG元素
其mount方法具体实现如下
ts
let isMounted = false
const app = {
//...
// app 挂载方法
mount(rootContainer: HostElement,isHydrate?: boolean,isSVG?: boolean) {
// 判断是否已经挂载
if (!isMounted) {
// #5571
// 如果挂载容器具有__vue_app__, 表示当前容器已就作为其他应用挂载容器
if (__DEV__ && (rootContainer as any).__vue_app__) {
warn(
`There is already an app instance mounted on the host container.\n` +
` If you want to mount another app on the same host container,` +
` you need to unmount the previous app by calling \`app.unmount()\` first.`
)
}
// 1. 调用createVNode 创建根组件的VNode
// rootComponent, rootProps 是createApp 调用时传入的参数, 根组件对象与props
const vnode = createVNode(rootComponent, rootProps)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
// 在根VNode上绑定应用上下文对象,在挂载完毕后绑定到根组件实例对象上
vnode.appContext = context
// HMR root reload
// 热更新
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}
// 2. 渲染 VNode
// 其他渲染方式
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 浏览器调用render 渲染根VNode
// render 函数在创建创建渲染函数中定义, 传递到createAppAPI, 通过闭包缓存
render(vnode, rootContainer, isSVG)
}
// 挂载完毕后, isMounted 状态设置为true
isMounted = true
// 应用实例上绑定挂载容器
app._container = rootContainer
// for devtools and telemetry
// 将app 应用实例对象绑定到容器上, 因此可以通过容器访问app 实例
;(rootContainer as any).__vue_app__ = app
// 开发环境调试
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
app._instance = vnode.component
devtoolsInitApp(app, version)
}
return getExposeProxy(vnode.component!) || vnode.component!.proxy
} else if (__DEV__) {
warn(
`App has already been mounted.\n` +
`If you want to remount the same app, move your app creation logic ` +
`into a factory function and create fresh app instances for each ` +
`mount - e.g. \`const createMyApp = () => createApp(App)\``
)
}
},
}
通过上面的分析, 在挂载应用实例时, 去除边缘判断,核心逻辑就以下两点
- 通过调用
createVNode创建虚拟节点, 参数为根组件对象和向根组件传入的props对象 - 通过调用
rendder方法渲染虚拟节点
接下来我们分别分析创建虚拟节点和渲染虚拟节点逻辑
3.4 createVNode 创建VNode 函数
虚拟节点的本质就是一个JS对象, 通过属性描述一个DOM 节点, 包括tag, props,children等
具体看一下createVNode函数的实现
ts
// 创建createVNode 函数
export const createVNode = ( __DEV__ ? createVNodeWithArgsTransform : _createVNode )
// 创建具有参数转换的VNode 函数
const createVNodeWithArgsTransform = (...args) => {
// 调用_createVNode 创建VNode
return _createVNode(
...(vnodeArgsTransformer
? vnodeArgsTransformer(args, currentRenderingInstance)
: args)
)
}
通过代码不难看出, 真正来创建虚拟节点的函数是_createVNode. 开发环境调用createVNodeWithArgsTransform也只不过根据vnodeArgsTransformer函数是否存在来转换一下参数, 最终函数返回_createVNode调用的结果
因此我们将通过_createVNode函数分析创建的虚拟节点(vnode) _createVNode的具体实现
ts
// _createVNode 第一个参数是必传的, 后面参数都具有默认值
function _createVNode(
type, // 创建VNode的类型
props = null,
children = null,
patchFlag = 0,
dynamicProps = null,
isBlockNode = false
) {
// type 不存在 或 type 值为 Symbol(v-ndc)
// 则提示type 无效, 并将type 认定为 Comment 注释类型
if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__ && !type) {
warn(`Invalid vnode type when creating vnode: ${type}.`)
}
type = Comment
}
// 判断type 是否已经是一个VNode
if (isVNode(type)) {
//...
}
// 调用isClassComponent 判断是否为有状态的组件
// class component normalization.
if (isClassComponent(type)) {
type = type.__vccOpts
}
// 2.x async/functional component compat
if (__COMPAT__) {
type = convertLegacyComponent(type, currentRenderingInstance)
}
// props 参数存在, 则规范处理class, style
// class & style normalization.
if (props) {
//...
}
// 判断VNode 标识, 采用二进制表示, 示例代码标识为 100 = 4
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
// 判断节点状态
if (__DEV__ && shapeFlag & ShapeFlags.STATEFUL_COMPONENT && isProxy(type)) {
//...
}
// 返回createBaseVNode 创建的VNode
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
从源码中可以看出, _createVNode函数只是对需要创建虚拟节点的参数进行各种判断检查, 规范props数据, 以及创建VNode 参数的节点标识 最后返回createBaseVNode函数创建的节点
createBaseVNode函数的实现如下:
ts
// 创建VNode
function createBaseVNode(
type,
props = null,
children = null,
patchFlag = 0,
dynamicProps = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
// 虚拟节点对象 VNode
const vnode = {
__v_isVNode: true,
__v_skip: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
slotScopeIds: null,
children,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null,
ctx: currentRenderingInstance
} as VNode
// ...
return vnode
}
可以看出createBaseVNode函数最大的作用就是创建VNode, 并返回该虚拟节点, 即JavaScript 对象
3.5 render 渲染VNode
在创建完虚拟DOM 后, 就会调用render方法渲染虚拟节点 render函数是在讲createRenderer的时候出现的,是在baseCreateRenderer中定义的
具体源码如下
ts
function baseCreateRenderer(options,createHydrationFns) {
//...
// render 渲染VNode 函数
const render = (vnode, container, isSVG) => {
// 如果VNode 不存在, 有可能之前已经挂载, 那么将会执行卸载操作
if (vnode == null) {
// container._vnode 表示上一次渲染的VNode, 存在则会执行卸载操作
if (container._vnode) {
unmount(container._vnode, null, null, true // 卸载VNode
}
} else {
// 如果VNode 存在, 则调用patch 方法, 判断处理除此渲染或更新渲染
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
// 刷新任务队列, 暂不分析
flushPreFlushCbs()
flushPostFlushCbs()
// 渲染完毕后, 容器记录渲染的VNode
container._vnode = vnode
}
//...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
render 渲染函数的主要作用就是分发
- 渲染的
vnode为空, 则判断之前已渲染的情况下,则调用unmount进行卸载操作 - 渲染的
vnode不为空, 则调用patch函数, 根据情况执行初始渲染或更新渲染操作
4. 初始化应用总结
分析到这里,以上的内容就是createApp源码的基本内容。主要作用就是确定渲染器,创建一个Vue应用实例,最后进行挂载,挂载之后具体渲染逻辑,后面分析
接下来对本节初始化应用进行总结,主要有两部分内容: 创建应用实例对象 和 挂载应用
4.1 创建应用实例对象
创建应用实例对象步骤:
- 调用
createApp函数创建应用实例 - 在
createApp函数中通过createRenderer函数创建渲染器 createRenderer函数返回渲染器对象(renderer):{render, createApp }- 调用渲染器中的
createApp方法创建应用实例对象 createApp函数中重写mount挂载方法,并返回应用实例对象
4.2 挂载应用
调用应用实例对象的mount方法进行挂载
- 首先在重写的
mount方法中处理获取挂载容器的DOM元素, 以及渲染根组件的模板 - 其后调用应用实例对象本身的
mount方法 - 在
mount挂载方法中根据根组件调用createVNode函数创建vnode - 之后在调用渲染器中的
render渲染函数, 渲染根组件生成的vnode
简单来说初始化应用就是做了如下几件事
- 创建
Vue应用实例对象。 - 确定应用挂载容器节点。
- 创建根组件
vnode对象 - 执行
render渲染根组件vnode。
至此, 整个createApp初始化应用我就带大家分析完了, 你可以回头去前言再看一眼createApp应用的流程图.
最后: 如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!