Appearance
第 10 章 组件系统
本章要点
- 组件实例的完整数据结构:从 ComponentInternalInstance 的 40+ 字段到它们各自的职责
- 组件创建的全流程:从 createComponentInstance 到 setupComponent 再到 setupRenderEffect
- Props 系统的深层机制:声明、解析、校验、响应式代理的四阶段流水线
- Emit 事件系统的实现:命名规范化、验证、监听器查找的完整链路
- Slots 的编译与运行时协作:静态 slots、动态 slots、作用域 slots 的统一处理
- expose 的安全边界:如何控制组件的公共 API 表面
- 异步组件与 Suspense 的协作机制
在前面的章节中,我们花了大量篇幅讨论响应式系统和编译器。但 Vue 的核心抽象不是 ref,不是模板,而是组件。组件是 Vue 开发者日常工作的基本单元——你创建组件、组合组件、在组件之间传递数据。响应式系统和编译器都是为组件服务的基础设施。
本章将深入组件系统的内部机制。我们不是在讨论"如何使用组件",而是在追问"组件是如何被创建、初始化、更新和销毁的"。当你在模板中写下 <MyComponent :msg="hello" /> 时,背后到底发生了什么?
10.1 组件实例的数据结构
ComponentInternalInstance:组件的"身份证"
每个 Vue 组件在运行时都对应一个 ComponentInternalInstance 对象。这个对象是组件系统的核心数据结构,它承载了组件从诞生到销毁的全部状态:
typescript
// packages/runtime-core/src/component.ts
export interface ComponentInternalInstance {
uid: number // 全局唯一 ID
type: ConcreteComponent // 组件定义(选项对象或 setup 函数)
parent: ComponentInternalInstance | null // 父组件实例
root: ComponentInternalInstance // 根组件实例
appContext: AppContext // 应用级上下文
// ---- VNode 相关 ----
vnode: VNode // 组件自身的 VNode
subTree: VNode // 组件渲染输出的 VNode 子树
next: VNode | null // 待更新的 VNode(父组件触发的更新)
// ---- 渲染相关 ----
render: InternalRenderFunction | null // 编译后的渲染函数
proxy: ComponentPublicInstance | null // 模板中的 `this` 代理
withProxy: ComponentPublicInstance | null // 带缓存的渲染代理
// ---- 状态相关 ----
setupState: Data // setup() 返回的状态
props: Data // 解析后的 props
attrs: Data // 非 prop 的 attributes(透传)
slots: InternalSlots // 插槽
refs: Data // 模板 ref 引用
// ---- 副作用相关 ----
effect: ReactiveEffect // 组件的渲染 effect
scope: EffectScope // 组件的 effect 作用域
update: SchedulerJob // 组件更新函数
// ---- 生命周期 ----
isMounted: boolean
isUnmounted: boolean
isDeactivated: boolean
// ---- 生命周期钩子 ----
bc: LifecycleHook // beforeCreate
c: LifecycleHook // created
bm: LifecycleHook // beforeMount
m: LifecycleHook // mounted
bu: LifecycleHook // beforeUpdate
u: LifecycleHook // updated
bum: LifecycleHook // beforeUnmount
um: LifecycleHook // unmounted
// ---- 其他 ----
emit: EmitFn // 事件发射函数
emitted: Record<string, boolean> | null
provides: Data // provide/inject 数据
exposed: Record<string, any> | null // expose 暴露的 API
exposeProxy: Record<string, any> | null
}40 多个字段,每一个都有明确的职责。这个结构体就像一个生物细胞——外表是统一的组件接口,内部是精密协作的功能模块。
为什么需要这么多字段?
你可能会疑问:一个组件真的需要这么多状态吗?答案是肯定的,因为组件身兼数职:
10.2 组件创建流程
从 VNode 到组件实例
当渲染器遇到一个组件类型的 VNode 时,会调用 mountComponent:
typescript
// packages/runtime-core/src/renderer.ts
const mountComponent = (
initialVNode: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
namespace: ElementNamespace,
optimized: boolean
) => {
// 第一步:创建组件实例
const instance: ComponentInternalInstance =
(initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 第二步:初始化组件(处理 props、slots、执行 setup)
setupComponent(instance)
// 第三步:建立渲染 effect
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
namespace,
optimized
)
}三步曲,清晰明了。让我们逐一深入。
第一步:createComponentInstance
typescript
// packages/runtime-core/src/component.ts
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
): ComponentInternalInstance {
const type = vnode.type as ConcreteComponent
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type,
parent,
appContext,
root: null!, // 稍后设置
subTree: null!, // 首次渲染时设置
effect: null!, // setupRenderEffect 中设置
update: null!, // setupRenderEffect 中设置
scope: new EffectScope(true /* detached */),
render: null,
proxy: null,
withProxy: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
// 状态
setupState: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
// 生命周期标记
isMounted: false,
isUnmounted: false,
isDeactivated: false,
// 生命周期钩子
bc: null, c: null, bm: null, m: null,
bu: null, u: null, bum: null, um: null,
emit: null!, // 稍后设置
emitted: null,
exposed: null,
exposeProxy: null,
next: null,
}
// 设置 root 引用
instance.root = parent ? parent.root : instance
// 创建 emit 函数
instance.emit = emit.bind(null, instance)
return instance
}