前言:
在源码分析概述中, 我们对源码源码结构进行了分析, 整个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
多包管理, 目前我们只知道createApp
API 是从vue
中结构出来的, 那么该API具体来自于哪个包中, 现在并不知道.
因此在分析createApp
API 前, 我们需要先确认该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
的入参, 用过createApp
API的朋友, 相信大家都知道, 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
方法的值是通过createApp
API方法创建的, 同时会将render
, hydrate
,两个方法作为参数传入createApp
API方法中.
所以接下来我们需要看一下createApp
API方法的实现
2.5 createApp
API 方法
createApp
API方法来自于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
}
}
源码中createApp
API函数返回了一个函数, 返回的这个函数才是真正的createApp
函数.
因此该返回的函数就是最后用来创建应用对象的createApp
方法
分析至此,我们就可以知道了
createApp
API 创建应用实例对象并返回createApp
API 中创建应用对象, 是通过调用渲染器{render,hydrate,createApp}
中的createApp
方法创建的- 渲染器中的
createApp
方法则是createApp
API函数的返回函数
而createApp
创建的应用对象就是包含了一些属性和常用的use
,mixins
,component
,directive
,mount
,unmount
,provide
方法的对象. 因此可以通过app
应用对象调用moun
t挂载应用
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应用的流程图.
最后: 如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!