第204集:Vue 3组件初始化流程深度解析
概述
在本集中,我们将深入剖析Vue 3组件的初始化流程,从组件创建到挂载的完整过程。理解组件初始化流程对于掌握Vue 3的组件系统至关重要,它涉及到组件的创建、挂载、更新和卸载等核心生命周期阶段。
组件初始化核心架构
Vue 3组件初始化流程主要包含以下核心阶段:
- 组件创建:创建组件实例
- 初始化选项:处理组件选项,如props、data、computed等
- 设置响应式:将组件数据转换为响应式
- 初始化生命周期:设置组件生命周期钩子
- 挂载组件:将组件渲染到DOM
- 更新组件:响应数据变化,更新组件
- 卸载组件:从DOM中移除组件,清理资源
源码目录结构
Vue 3组件系统的源码主要位于packages/runtime-core/src/components/目录下:
packages/runtime-core/src/components/
├── component.ts # 组件核心实现
├── componentProps.ts # 组件属性处理
├── componentEmits.ts # 组件事件处理
├── componentSlots.ts # 组件插槽处理
├── KeepAlive.ts # KeepAlive组件
├── Suspense.ts # Suspense组件
└── Teleport.ts # Teleport组件核心源码解析
1. 组件创建
组件创建的入口是createComponentInstance函数,定义在runtime-core/src/component.ts中:
// packages/runtime-core/src/component.ts
/**
* 创建组件实例
* @param vnode 组件VNode
* @param parent 父组件实例
* @returns 组件实例
*/
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null = null
): ComponentInternalInstance {
// 创建组件实例
const instance: ComponentInternalInstance = {
vnode,
type: vnode.type as ComponentOptions,
parent,
appContext: parent ? parent.appContext : vnode.appContext!,
root: parent ? parent.root : null as any, // will be set immediately below
next: null,
subTree: null as any,
update: null as any, // will be set immediately below
render: null as any,
proxy: null as any,
exposed: null,
exposeProxy: null,
withProxy: null,
propsOptions: normalizePropsOptions(vnode.type as ComponentOptions),
emitsOptions: normalizeEmitsOptions(vnode.type as ComponentOptions),
inheritAttrs: vnode.type.inheritAttrs !== false,
slots: null,
attrs: null,
props: shallowReactive({}),
emit: null as any, // will be set immediately below
eventEmitter: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
injects: null,
effects: null,
computed: null,
data: null,
setupState: null,
setupContext: null,
renderContext: null,
scopeId: vnode.type.__scopeId,
slotsScopeIds: vnode.type.__slotsScopeIds,
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
suspense,
asyncDep: null,
asyncResolved: false,
// ... 其他属性
}
// 设置根实例
instance.root = parent ? parent.root : instance
// 创建更新函数
instance.update = () => queueJob(updateComponent, instance)
// 创建emit函数
instance.emit = emit.bind(null, instance)
return instance
}2. 组件挂载
组件挂载的入口是mountComponent函数,定义在runtime-core/src/renderer.ts中:
// packages/runtime-core/src/renderer.ts
/**
* 挂载组件
* @param initialVNode 初始VNode
* @param container 容器
* @param anchor 锚点
* @param parentComponent 父组件
* @param parentSuspense 父悬念
* @param namespace 命名空间
* @param slotScopeIds 插槽作用域id
* @param optimized 是否优化
*/
const mountComponent = (initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, namespace: RendererNamespace | null, slotScopeIds: string[] | null, optimized: boolean) => {
// 创建组件实例
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense))
// 设置实例的容器和锚点
instance.container = container
instance.anchor = anchor
instance.namespace = namespace
instance.slotScopeIds = slotScopeIds
instance.optimized = optimized
// 初始化组件
// 1. 处理props
// 2. 处理inject
// 3. 调用setup函数
// 4. 处理data
// 5. 处理computed
// 6. 处理watch
// 7. 处理生命周期钩子
setupComponent(instance)
// 设置组件渲染上下文
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, namespace, slotScopeIds, optimized)
}
/**
* 设置组件
* @param instance 组件实例
*/
export function setupComponent(instance: ComponentInternalInstance) {
// 处理组件VNode
const { props, children } = instance.vnode
// 初始化props
initProps(instance, props, (instance.type as ComponentOptions).props)
// 初始化插槽
initSlots(instance, children)
// 设置组件状态:调用setup函数或初始化data
const setupResult = setupStatefulComponent(instance)
return setupResult
}
/**
* 设置有状态组件
* @param instance 组件实例
*/
function setupStatefulComponent(instance: ComponentInternalInstance) {
const Component = instance.type as ComponentOptions
// 创建渲染上下文
instance.renderContext = instance.scopeId ? Object.create(null) : instance.proxy as any
// 调用setup函数
const { setup } = Component
if (setup) {
// 创建setup上下文
const setupContext = (instance.setupContext = createSetupContext(instance))
// 调用setup函数
const setupResult = setup(shallowReadonly(instance.props), setupContext)
// 处理setup返回值
handleSetupResult(instance, setupResult)
} else {
// 初始化组件状态
finishComponentSetup(instance)
}
}3. 初始化Props
initProps函数负责初始化组件的props,定义在runtime-core/src/componentProps.ts中:
// packages/runtime-core/src/componentProps.ts
/**
* 初始化组件props
* @param instance 组件实例
* @param rawProps 原始props
* @param internalPropsOptions 内部props选项
*/
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
internalPropsOptions: ComponentPropsOptions | null
) {
// 获取props选项
const propsOptions = internalPropsOptions || instance.propsOptions
// 标准化props
const props = instance.props
// 原始props
const attrs = (instance.attrs = {})
// 如果没有props选项,将所有属性作为attrs
if (!propsOptions) {
if (rawProps) {
for (const key in rawProps) {
attrs[key] = rawProps[key]
}
}
return
}
// 处理props
if (rawProps) {
for (const key in rawProps) {
const value = rawProps[key]
// 检查是否是props
const propOptions = propsOptions[key]
if (propOptions != null) {
// 是props,添加到props对象
props[key] = value
} else {
// 不是props,添加到attrs对象
attrs[key] = value
}
}
}
}4. 初始化Data
initData函数负责初始化组件的data,将其转换为响应式:
// packages/runtime-core/src/component.ts
/**
* 初始化组件data
* @param instance 组件实例
*/
function initData(instance: ComponentInternalInstance) {
const { data } = instance.type as ComponentOptions
if (data) {
// 调用data函数获取data对象
const dataFn = typeof data === 'function' ? data : () => data
const dataObj = dataFn.call(instance.proxy, instance.proxy)
// 将data对象转换为响应式
instance.data = reactive(dataObj)
// 将data属性添加到渲染上下文
for (const key in dataObj) {
Object.defineProperty(instance.renderContext, key, {
get() {
return instance.data[key]
},
set(val) {
instance.data[key] = val
},
enumerable: true,
configurable: true
})
}
}
}5. 初始化Computed
initComputed函数负责初始化组件的computed属性:
// packages/runtime-core/src/component.ts
/**
* 初始化组件computed
* @param instance 组件实例
*/
function initComputed(instance: ComponentInternalInstance) {
const { computed } = instance.type as ComponentOptions
if (computed) {
const computedEntries = Object.entries(computed)
const computedObj = (instance.computed = {})
// 遍历computed属性
for (const [key, value] of computedEntries) {
// 创建computed属性
computedObj[key] = computed(() => {
// 计算属性的getter函数
const getter = typeof value === 'function' ? value : value.get
return getter.call(instance.proxy)
})
// 将computed属性添加到渲染上下文
Object.defineProperty(instance.renderContext, key, {
get() {
return computedObj[key].value
},
enumerable: true,
configurable: true
})
}
}
}6. 设置渲染效果
setupRenderEffect函数负责设置组件的渲染效果,定义在runtime-core/src/renderer.ts中:
// packages/runtime-core/src/renderer.ts
/**
* 设置渲染效果
* @param instance 组件实例
* @param initialVNode 初始VNode
* @param container 容器
* @param anchor 锚点
* @param parentSuspense 父悬念
* @param namespace 命名空间
* @param slotScopeIds 插槽作用域id
* @param optimized 是否优化
*/
const setupRenderEffect = (instance: ComponentInternalInstance, initialVNode: VNode, container: RendererElement, anchor: RendererNode | null, parentSuspense: SuspenseBoundary | null, namespace: RendererNamespace | null, slotScopeIds: string[] | null, optimized: boolean) => {
// 创建渲染效果
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次挂载
// 1. 调用渲染函数生成vnode
const subTree = (instance.subTree = renderComponentRoot(instance))
// 2. 挂载子树
patch(null, subTree, container, anchor, instance, parentSuspense, namespace, slotScopeIds, optimized)
// 3. 设置组件实例的el
initialVNode.el = subTree.el
// 4. 调用mounted生命周期钩子
instance.isMounted = true
callHook(instance, LifecycleHooks.MOUNTED)
} else {
// 更新组件
// 1. 保存旧的子树
const prevTree = instance.subTree
// 2. 调用渲染函数生成新的vnode
const nextTree = (instance.subTree = renderComponentRoot(instance))
// 3. 执行diff算法,更新DOM
patch(prevTree, nextTree, container, anchor, instance, parentSuspense, namespace, slotScopeIds, optimized)
// 4. 设置组件实例的el
nextTree.el = prevTree.el
initialVNode.el = nextTree.el
// 5. 调用updated生命周期钩子
callHook(instance, LifecycleHooks.UPDATED)
}
}, {
scheduler: queueJob,
onTrack: instance.rtc && instance.rtc.onTrack,
onTrigger: instance.rtc && instance.rtc.onTrigger
})
}7. 渲染组件根节点
renderComponentRoot函数负责调用组件的渲染函数,生成组件的子树VNode:
// packages/runtime-core/src/componentRenderUtils.ts
/**
* 渲染组件根节点
* @param instance 组件实例
* @returns 组件子树VNode
*/
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const { type: Component, vnode, render, props, slots, emit, expose } = instance
// 如果有render函数,调用render函数
if (render) {
// 调用render函数,生成vnode
const subTree = render.call(
instance.proxy,
// 渲染上下文
instance.renderContext,
// 渲染选项
{
props,
slots,
emit,
expose
}
)
return subTree
} else if (Component.template) {
// 如果有template,编译template为render函数,然后调用
// ... 编译template的逻辑
} else {
// 没有render函数和template,返回空文本节点
return createVNode('div', null, 'Component has no render function.')
}
}8. 组件卸载
组件卸载的入口是unmountComponent函数,定义在runtime-core/src/renderer.ts中:
// packages/runtime-core/src/renderer.ts
/**
* 卸载组件
* @param instance 组件实例
* @param parentSuspense 父悬念
* @param doRemove 是否移除DOM
*/
const unmountComponent = (instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null, doRemove?: boolean) => {
// 调用beforeUnmount生命周期钩子
if (instance.isMounted) {
callHook(instance, LifecycleHooks.BEFORE_UNMOUNT)
}
// 卸载子树
if (instance.subTree) {
unmount(instance.subTree, instance, parentSuspense, doRemove)
}
// 调用unmounted生命周期钩子
instance.isUnmounted = true
callHook(instance, LifecycleHooks.UNMOUNTED)
// 清理资源
cleanupComponent(instance)
}
/**
* 清理组件资源
* @param instance 组件实例
*/
function cleanupComponent(instance: ComponentInternalInstance) {
// 清理响应式依赖
if (instance.effects) {
for (const effect of instance.effects) {
effect.stop()
}
}
// 清理事件监听器
if (instance.eventEmitter) {
instance.eventEmitter.clear()
}
// 清理其他资源
// ... 清理逻辑
}组件生命周期钩子
Vue 3组件的生命周期钩子定义在runtime-core/src/enums.ts中:
// packages/runtime-core/src/enums.ts
/**
* 生命周期钩子枚举
*/
export const enum LifecycleHooks {
BEFORE_CREATE = 0,
CREATED = 1,
BEFORE_MOUNT = 2,
MOUNTED = 3,
BEFORE_UPDATE = 4,
UPDATED = 5,
BEFORE_UNMOUNT = 6,
UNMOUNTED = 7,
DEACTIVATED = 8,
ACTIVATED = 9,
RENDER_TRIGGERED = 10,
RENDER_TRACKED = 11,
ERROR_CAPTURED = 12,
SERVER_PREFETCH = 13
}组件初始化流程示例
让我们通过一个简单的组件来演示完整的初始化流程:
组件定义
const App = {
props: ['message'],
setup(props) {
const count = ref(0)
const increment = () => count.value++
return {
count,
increment
}
},
template: `
<div>
<h1>{{ message }}</h1>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
`
}初始化流程
- 创建组件VNode:
createVNode(App, { message: 'Hello Vue 3' }) - 创建组件实例:
createComponentInstance(vnode, null) - 初始化Props:
initProps(instance, { message: 'Hello Vue 3' }) - 调用setup函数:执行
setup({ message: 'Hello Vue 3' }),返回{ count: ref(0), increment: () => count.value++ } - 处理setup返回值:将返回值转换为响应式,并添加到渲染上下文
- 编译模板:将template编译为render函数
- 设置渲染效果:创建effect,监听数据变化
- 首次渲染:调用render函数生成VNode,执行patch,将组件挂载到DOM
- 调用mounted钩子:组件挂载完成后调用
组件更新流程
当组件数据变化时,会触发组件更新流程:
- 数据变化:
count.value++ - 触发响应式更新:响应式系统检测到数据变化,触发相关effect
- 执行组件更新:调用组件的update函数,进入更新流程
- 生成新的VNode:调用render函数生成新的VNode
- 执行Diff算法:比较新旧VNode,计算差异
- 应用差异:将差异应用到DOM
- 调用updated钩子:组件更新完成后调用
组件卸载流程
当组件需要从DOM中移除时,会触发组件卸载流程:
- 调用beforeUnmount钩子:在组件卸载前调用
- 卸载子树:递归卸载组件的子树
- 调用unmounted钩子:在组件卸载后调用
- 清理资源:清理响应式依赖、事件监听器等资源
性能优化建议
了解组件初始化流程后,我们可以给出一些性能优化建议:
- 合理使用setup函数:setup函数只在组件创建时执行一次,避免在其中做过多计算
- 使用shallowRef和shallowReactive:对于深层嵌套的数据,使用shallowRef和shallowReactive可以提高性能
- 合理使用computed:将复杂计算逻辑放在computed中,利用其缓存特性
- 避免不必要的渲染:使用v-memo、v-once等指令,避免不必要的组件渲染
- 合理拆分组件:将复杂组件拆分为更小的组件,减少渲染的范围
- 清理资源:在unmounted钩子中清理定时器、事件监听器等资源
总结
本集深入剖析了Vue 3组件初始化流程的源码实现,包括组件创建、挂载、更新和卸载等核心阶段。理解组件初始化流程对于掌握Vue 3的组件系统至关重要,它涉及到组件的创建、挂载、更新和卸载等核心生命周期阶段。
Vue 3的组件系统通过一系列优化,如Composition API、响应式系统改进等,显著提升了组件的性能和开发体验。通过合理使用Vue 3的组件系统,我们可以写出高效、可靠的组件。
在后续的源码解析系列中,我们将继续深入探讨Vue 3的其他核心模块,包括生命周期实现、指令系统等。