「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。
前言
前文 Vue3.0源码学习——初始化流程分析(1.实例创建过程) 了解了Vue在创建实例后返回了一个对象,这个对象里有一个挂载函数 mount,这篇文章就来学习一下Vue挂载的过程
查看挂载函数的调用栈
- 在
createAppAPI函数中找到mount的定义,位置在/packages/runtime-core/src/apiCreateApp.ts,这就是初始化createApp().mount('#app')调用的挂载函数,一开始还未挂载,因此isMounted === false在这里打上一个断点
- 看到在两处调用了
mount函数
- 第一步在
todomvc中初始化调用了
-
第二步在
createApp(/packages/runtime-dom/src/index.ts) 中实例app上重新定义了mount方法,其中执行了原始的mount方法,目的是为了扩展,兼容浏览器环境,这里不做展开将在后序篇章中进行学习 -
通过查看调用栈,能够比较清晰和快速的了解原始
mount函数在初始化过程中在哪些地方进行了调用
在源码中查看挂载函数的执行流程
- 首先第一次执行
mount组件还未挂载,isMounted === false走if分支
mount(
: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) { // 走这里
// 1. 创建根组件的vnode,将真实dom转为虚拟dom
const vnode = createVNode(
as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
// HMR root reload
if (__DEV__) {
context.reload = () => {
render(cloneVNode(vnode), rootContainer, isSVG)
}
}
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
// 2. 将虚拟dom渲染转为真实dom
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
// for devtools and telemetry
;(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
}
...
-
- 创建根组件的vnode,将真实dom转为虚拟dom,
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
为什么要用虚拟dom:创建真实DOM的代价高,真实的 dom 节点实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
-
- 将虚拟dom渲染转为真实dom
render(vnode, rootContainer, isSVG)
- 这个
render函数在上一章介绍过,在Vue3中最大的渲染函数baseCreateRenderer中定义,并在执行createAppAPI时作为第一个参数传入,位置packages\runtime-core\src\renderer.ts
// 创建渲染函数
function baseCreateRenderer(
options: RendererOptions,
createHydrationFns?: typeof createHydrationFunctions
): any {
...
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 首次patch第一个参数为null
// 首次patch是挂载,不更新
// 此处vnode会被patch转化为真实dom对象,并追加到container容器中
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPostFlushCbs()
container._vnode = vnode
}
...
return {
render,
hydrate,
createApp: createAppAPI(render, hydrate)
}
}
patch函数的执行将在下一篇文章中进行学习
小结
挂载的流程就是生成 vnode 传递给 patch 函数转换为真实dom并挂载在宿主元素上