本篇接上篇入口函数,以一个简单的事例(后续称之为本例)详细解析mount之后的操作(解析传入的options参数、template模版编译、生成虚拟DOM对象、虚拟DOM对象解析生成真正的DOM、数据修改之后如何触发DOM的重新渲染等等)
简单事例(以Vue3新增的setup方法为例)
<div id='app'>
{{message}}
<button @click="modifyMessage">修改数据</button>
</div>
const { ref, createApp } = Vue
const app = createApp({
// Vue3新增的setup属性
setup(props) {
const message = ref('测试数据')
const modifyMessage = () => {
message.value = '修改后的测试数据'
}
return {
message,
modifyMessage
}
}
}).mount('#app')
app.mount
const proxy = mount(container, false, container instanceof SVGElement)
Vue3通过createApp方法生成了app对象,接着调用app.mount方法获取了root根节点,然后调用原始的mount方法进行挂载,来看下初始的mount内部实现
// context对象的生成
const context = createAppContext()
// 原始的mount函数
mount(
rootContainer: HostElement,
isHydrate?: boolean,
isSVG?: boolean
): any {
if (!isMounted) {
const vnode = createVNode(
rootComponent as ConcreteComponent,
rootProps
)
vnode.appContext = context
// ...
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
isMounted = true
app._container = rootContainer
(rootContainer as any).__vue_app__ = app
// ...
return vnode.component!.proxy
}
}
mount方法首先调用createVNode方法创建vnode对象,简单看下createVNode方法的内部实现(主要是看下shapeFlag变量的生成,与后续调用patch方法关联)
export const createVNode = (
__DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode
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 {
// ...
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
)
}
// createBaseVNode方法
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 = {
type,
key,
props,
shapeFlag,
patchFlag,
// ...
}
// ...
return vnode
}
可以看到shapeFlag是根据调用此方法的rootComponent对象也就是我们调用createApp方法传入的参数类型决定的。本例中我们传入的是一个对象,所以shapeFlag的值为ShapeFlags.STATEFUL_COMPONENT(1 << 2 = 4)。之后设置vnode的appContext的属性为context对象(该对象是调用createAppContext方法生成的,后续使用到context对象属性时再详细解析)。接着我们再回到mount方法
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer, isSVG)
}
因为传入的isHydrate为false,所以调用render方法,参数为vnode对象、root根节点以及false(isSVG是container instanceof SVGElement的值,本例并非svg元素)。调用的render函数是createAppAPI方法的传参,而createAppAPI方法是在baseCreateRenderer方法中调用的。回归到baseCreateRenderer方法中查看render方法
const render: RootRenderFunction = (vnode, container, isSVG) => {
// vnode是存在的
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
}
// ...
}
使用patch解析根组件
因为vnode是存在的,所以调用patch方法
patch(null, vnode, container, null, null, null, false)
// patch方法
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:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// ...
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
// ...
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
// ...
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
patch方法会根据type的值选择调用函数,本例type为{setup: setup(props){}}是一个对象,所以进入default分支,因为vnode的shapeFlag刚才解析为4(4 & (1 << 2 | 1 << 1) = 4),因此调用processComponent方法(处理组件)
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) {
// 组件为keep-alive
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {}
else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {}
}
挂载根组件
之后调用mountComponent方法
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const compatMountInstance =
__COMPAT__ && initialVNode.isCompatRoot && initialVNode.component
const instance: ComponentInternalInstance =
compatMountInstance ||
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// ...
// resolve props and slots for setup context
if (!(__COMPAT__ && compatMountInstance)) {
if (__DEV__) {
startMeasure(instance, `init`)
}
setupComponent(instance)
if (__DEV__) {
endMeasure(instance, `init`)
}
}
// ...
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
// ...
}
内部主要是通过createComponentInstance方法创建instance对象(创建一个对象,并利用Object.defineProperty给对象增加_属性,值即为instance对象,对象的vnode属性即为调用时传入的vnode。还有一些其他的属性,后续使用到再详细解析)。然后调用setupComponent(instance)方法。
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
// ...
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
// ...
instance.accessCache = Object.create(null)
instance.proxy = markRaw(new Proxy(instance.ctx, PublicInstanceProxyHandlers))
// 2. call setup()
const { setup } = Component
if (setup) {
// ...
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
)
// ...
}
}
setupComponent方法主要调用了setupStatefulComponent方法,此方法的内部主要是将instance.ctx使用new Proxy()代理,instance.ctx对象中是_ 、$、$el等属性,而代理的钩子函数对象是PublicInstanceProxyHandlers
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {},
set(
{ _: instance }: ComponentRenderContext,
key: string,
value: any
): boolean {},
has(
{
_: { data, setupState, accessCache, ctx, appContext, propsOptions }
}: ComponentRenderContext,
key: string
) {}
}
PublicInstanceProxyHandlers包含了get、set、has三个陷阱钩子函数,后续触发时我们再详细解析。回到setupStatefulComponent函数中,当setup属性存在时(本例存在),开始运行setup函数,解析函数体中的数据及方法。
总结
原始的mount挂载方法中主要是利用createVNode创建了vnode节点,参数type是调用createApp方法传入的,根据type的类型解析出shapeFlag。然后调用patch方法,根据shapeFlag的值判断是调用哪个方法。解析组件时调用mountComponent方法,该方法中创建了instance对象,其中对象的ctx属性新增了_、$、$el等属性,instance对象的vnode属性即为创建的vnode对象,而vnode对象的component属性为instance对象,二者相互关联。之后利用new Proxy代理instance.ctx对象,设置get、set、has钩子函数。之后会解析setup方法,看看是如何给传入的数据设置钩子函数的。