前言
这是vue3系列源码的第二章,使用的vue3版本是3.2.45
mount的流程
js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
这是我们在vue3项目中main.js
文件中经常出现的代码,在上一篇,我们分析了createApp
的整个流程, 这个函数最终返回的是一个有mount
属性的对象。
这里我们老规矩,先把打个断点,看一下调用了mount
的这个app
对象长啥样。
那么我们就看一下,这个mount
函数到底干了什么。
在runtime-dom
部分中的createApp
函数中,有这么一段:
js
const { mount } = app
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
const container = normalizeContainer(containerOrSelector)
if (!container) return
const component = app._component
// clear content before mounting
container.innerHTML = ''
const proxy = mount(container, false, container instanceof SVGElement)
if (container instanceof Element) {
container.removeAttribute('v-cloak')
container.setAttribute('data-v-app', '')
}
return proxy
}
这里的normalizeContainer
函数其实是将我们传入的#app
字符串,变成了DOM对象。
接下来,调用了在之前在runtime-core
部分中createAppAPI
函数中定义好的mount
函数。
mount
js
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted)
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
// store app context on the root VNode.
// this will be set on the root instance on initial mount.
vnode.appContext = context
if (isHydrate && hydrate) {
...
} else {
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
} else if (__DEV__) {
warn(...)
}
}
在上一篇createApp
的文章中,我们有看到函数中执行了这么一句代码
js
let isMounted = false
所以,在这里的if条件语句是true,会执行createVNode
函数。
从代码上看也能很明白的看清楚,
js
const vnode = createVNode( rootComponent as ConcreteComponent, rootProps )
createVNode
函数应该是返回了一个vnode
对象。
然后把我们上一篇文章中提到的这个context
对象挂载到vnode
中。
js
vnode.appContext = context
接着就执行了render
函数。
js
render(vnode, rootContainer, isSVG)
render
函数执行完之后会把isMounted置为true。
在最后,mount
返回了getExposeProxy
函数的执行,它主要用于实现 Composition API 中的 expose
方法。
下面我们按个去仔细看看上面提到的这些函数到底是长啥样。
首先createVNode
函数的实质其实是 _createVNode()
函数
_createVNode
js
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
// 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
}
return createBaseVNode(
type,
props,
children,
patchFlag,
dynamicProps,
shapeFlag,
isBlockNode,
true
)
}
这里传进来的参数中,type 其实就是我们的App.vue
这个函数中真正执行了的代码,其实就上面这么点。
shapeFlag
是一个位掩码(bitmask),用于表示虚拟节点(VNode)的类型和特征,
它的每个比特位代表了不同的虚拟节点类型或特征。在 Vue 3 中,它被用于快速判断和识别虚拟节点的类型,从而在处理虚拟节点时进行相应的优化和处理。
我们的type这里是一个object,所以是ShapeFlags.STATEFUL_COMPONENT
, 值是4。
_createVNode
函数的最后,返回了另一个函数的执行,createBaseVNode
函数。
createBaseVNode
js
function createBaseVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag = 0,
dynamicProps: string[] | null = null,
shapeFlag = type === Fragment ? 0 : ShapeFlags.ELEMENT,
isBlockNode = false,
needFullChildrenNormalization = false
) {
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
}
这个函数其实就是返回了一个vnode
对象。
render
这个render
函数是定义在runtime-core
部分的函数
js
const render: RootRenderFunction = (vnode, container, isSVG) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
flushPreFlushCbs()
flushPostFlushCbs()
container._vnode = vnode
}
我们先确认一下传进来的参数:
vnode
是上一个_createVNode
函数生成的对象,container
是根据我们传进来的#app
生成的DOM对象,isSVG
是false。
所以我们直接走到了patch
函数,对组件进行各种处理。
接下来是flushPreFlushCbs
函数,用于执行预处理的回调函数。
接下来是flushPostFlushCbs
函数,用于执行后处理回调的函数。
这两个函数我们将在后面的部分详解。
最后,把vnode挂载到DOM对象上。
patch
patch
函数是在上一篇文章中说的baseCreateRenderer
中定义的。
js
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
...
break
case Static:
...
break
case Fragment:
...
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(...)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(...)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(...)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
老规矩,我们先看一下传入的参数:
n1
为nulln2
为生成的vnodecontainer
为DOM对象
后面的参数都是默认取值
上面已经说过,我们的type其实就是App.vue
文件, 所以switch流程会走到default里面进行判断。
上面也提过,我们的shapeFlag的值是4,在这里做的位运算里面,和ShapeFlags.COMPONENT
的值6,进行按位与得到的值是4,为true
按位与:将各个数位的数字进行逻辑与,都是 1 才为 1,否则为 0。
100 & 110 = 100
那么接下来就进入到processComponent
,这个函数对组件进行一系列的处理。
processComponent
processComponent
函数和patch
一样,也是在baseCreateRenderer
函数中定义的
js
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean
) => {
n2.slotScopeIds = slotScopeIds
if (n1 == null) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
;(parentComponent!.ctx as KeepAliveContext).activate(...)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
看一下参数:
n1
为nulln2
为vnodecontainer
为DOM对象
我们这里的n1为null,ShapeFlags.COMPONENT_KEPT_ALIVE
的值是512,按位与的结果是0,所以直接进入mountComponent
函数。
mountComponent
大家肯定都能猜得到,mountComponent
函数还是在baseCreateRenderer
函数中定义的,就在processComponent
的下一个。
js
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
setupComponent(instance)
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
这里先调用了createComponentInstance
函数,得到了组件实例。
接着调用了setupComponent
,主要负责在创建组件实例时,初始化组件的各项配置,设置组件的状态、属性、事件等内容。
最后调用了setupRenderEffect
,他是 Vue 3 源码中负责设置渲染效果的一部分。在 Vue 中,渲染函数是由 setupRenderEffect
来管理的,它的作用是确保组件的渲染能够响应数据的变化并进行更新。
createComponentInstance
createComponentInstance
函数是定义在runtime-core
中。
js
function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
// inherit parent app context - or - if root, adopt from root vnode
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type,
parent,
appContext,
root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
effect: null!,
update: null!, // will be set synchronously right after creation
scope: new EffectScope(true /* detached */),
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
withProxy: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
renderCache: [],
// local resolved assets
components: null,
directives: null,
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
emit: null!, // to be set immediately
emitted: null,
// props default value
propsDefaults: EMPTY_OBJ,
// inheritAttrs
inheritAttrs: type.inheritAttrs,
// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
sp: null
}
if (__DEV__) {
instance.ctx = createDevRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
}
这个函数的主要作用就是生成了一个实例对象并返回。 实例中的scope
属性
js
scope: new EffectScope(true /* detached */)
这个EffectScope类是定义在reactivity
部分的。
EffectScope
类是 Vue 3 源码中用于管理副作用函数执行的环境和依赖关系的重要组成部分。它主要用于跟踪和管理副作用函数,确保它们能够按照正确的顺序执行,并正确处理它们之间的依赖关系。
我们将在后面的文章中展开分析,这里不做展开。 normalizePropsOptions
和normalizeEmitsOptions
分别是对props
和emits
做规范化处理的。
instance的ctx字段指向的其实是instance自己。
这里因为是根组件,所以root字段指向的也是自己。
setupComponent
setupComponent
函数是定义在runtime-core
部分的
js
function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children } = instance.vnode
const isStateful = isStatefulComponent(instance)
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
setupComponent
主要负责:
- 创建组件实例:在组件初始化时创建组件实例,处理组件的状态、方法以及响应式数据等。
- 设置组件的
props
、slots
和attrs
:处理组件的属性、插槽和特性等,确保它们被正确地传递和管理。 - 处理生命周期钩子 :包括
beforeCreate
、created
、beforeMount
、mounted
等生命周期钩子的调用和管理。 - 准备渲染上下文:为组件的渲染提供上下文环境,并准备好响应式更新的机制。
- 处理渲染函数 :设置组件的
render
函数,确定组件如何渲染和更新。
这里我们不做展开。
setupRenderEffect
setupRenderEffect
函数也是在baseCreateRenderer
函数中定义的。
js
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
const componentUpdateFn = () => {...}
// create reactive effect for rendering
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
() => queueJob(update),
instance.scope // track it in component's effect scope
))
const update: SchedulerJob = (instance.update = () => effect.run())
update.id = instance.uid
// allowRecurse
// #1801, #2043 component render effects should allow recursive updates
toggleRecurse(instance, true)
update()
}
在 Vue 3 的源码中,setupRenderEffect
主要负责以下工作:
- 建立响应式关联:将组件的渲染函数与数据之间建立响应式的关联,以确保当数据发生变化时能够触发重新渲染。
- 收集依赖:在渲染函数中收集数据的依赖,当这些依赖的数据发生变化时,触发重新渲染。
- 设置副作用函数:建立数据变化时的副作用函数,这个函数用于执行实际的更新操作,例如调用渲染函数来生成新的虚拟 DOM 并进行比对,最终更新视图。
- 处理更新时机:管理组件的更新时机,包括何时触发首次渲染、何时进行数据变化时的更新等。
这里我们不做展开。
getExposeProxy
getExposeProxy
定义在runtime-core
部分。
js
function getExposeProxy(instance: ComponentInternalInstance) {
if (instance.exposed) {
return (
instance.exposeProxy ||
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
get(target, key: string) {
if (key in target) {
return target[key]
} else if (key in publicPropertiesMap) {
return publicPropertiesMap[key](instance)
}
},
has(target, key: string) {
return key in target || key in publicPropertiesMap
}
}))
)
}
}
这里有两个点注意一下:
markRaw
函数是用于标记对象,使其成为"非响应式"的。这意味着当对象被标记为markRaw
后,Vue 的响应式系统将不会对其进行代理,也不会进行响应式追踪。proxyRefs
函数是 Vue 3 源码中的一个实用工具函数,用于将 ref 对象的包装进行代理。这个函数的作用是为了确保在访问 ref 对象中的值时,能够自动获取其 value 属性,同时保持响应式。
所以proxyRefs(markRaw(instance.exposed))
用于处理通过 expose
方法暴露给组件外部的属性和方法,确保这些属性和方法既不会触发响应式更新,又能在访问时自动获取其值。
至此,就是mount
的全部过程。