Vue.js 源码揭秘(四):组件系统
本文深入 runtime-core 源码,解析组件实例、setup 执行、生命周期的实现。
一、组件实例
1.1 ComponentInternalInstance
// packages/runtime-core/src/component.ts
interface ComponentInternalInstance {
uid: number // 唯一标识
type: ConcreteComponent // 组件定义
parent: ComponentInternalInstance | null
root: ComponentInternalInstance
appContext: AppContext
// VNode
vnode: VNode
subTree: VNode // 渲染的子树
next: VNode | null // 待更新的 VNode
// 状态
data: Data
props: Data
attrs: Data
slots: InternalSlots
refs: Data
emit: EmitFn
// setup 相关
setupState: Data // setup() 返回值
setupContext: SetupContext | null
// 渲染
render: InternalRenderFunction | null
effect: ReactiveEffect
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
// 其他
provides: Data
accessCache: Data | null
renderCache: (Function | VNode)[]
}
1.2 createComponentInstance
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!,
next: null,
subTree: null!,
effect: null!,
update: null!,
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
// 状态
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// 生命周期
isMounted: false,
isUnmounted: false,
isDeactivated: false,
// 钩子
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
// 其他
emit: null!,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null,
renderCache: []
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
}
二、组件挂载
2.1 processComponent
const processComponent = (
n1: VNode | null,
n2: VNode,
container,
anchor,
parentComponent,
parentSuspense,
namespace,
slotScopeIds,
optimized
) => {
if (n1 == null) {
// 挂载
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// KeepAlive 激活
parentComponent.ctx.activate(n2, container, anchor, ...)
} else {
mountComponent(n2, container, anchor, ...)
}
} else {
// 更新
updateComponent(n1, n2, optimized)
}
}
2.2 mountComponent
const mountComponent = (
initialVNode: VNode,
container,
anchor,
parentComponent,
parentSuspense,
namespace,
optimized
) => {
// 1. 创建组件实例
const instance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// 2. 设置组件
setupComponent(instance)
// 3. 设置渲染副作用
setupRenderEffect(instance, initialVNode, container, anchor, ...)
}
三、setup 执行
3.1 setupComponent
export function setupComponent(instance: ComponentInternalInstance) {
const { props, children } = instance.vnode
const isStateful = instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
// 初始化 props
initProps(instance, props, isStateful)
// 初始化 slots
initSlots(instance, children)
// 执行 setup
const setupResult = isStateful
? setupStatefulComponent(instance)
: undefined
return setupResult
}
3.2 setupStatefulComponent
function setupStatefulComponent(instance: ComponentInternalInstance) {
const Component = instance.type as ComponentOptions
// 创建渲染代理
instance.accessCache = Object.create(null)
instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
// 执行 setup
const { setup } = Component
if (setup) {
// 创建 setup 上下文
const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null)
// 设置当前实例
setCurrentInstance(instance)
pauseTracking()
// 执行 setup
const setupResult = callWithErrorHandling(
setup,
instance,
ErrorCodes.SETUP_FUNCTION,
[instance.props, setupContext]
)
resetTracking()
unsetCurrentInstance()
// 处理 setup 返回值
if (isPromise(setupResult)) {
// 异步 setup
setupResult.then(
result => handleSetupResult(instance, result),
err => handleError(err, instance, ErrorCodes.SETUP_FUNCTION)
)
} else {
handleSetupResult(instance, setupResult)
}
} else {
finishComponentSetup(instance)
}
}
3.3 handleSetupResult
function handleSetupResult(instance, setupResult) {
if (isFunction(setupResult)) {
// setup 返回 render 函数
instance.render = setupResult
} else if (isObject(setupResult)) {
// setup 返回状态对象
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance)
}
function finishComponentSetup(instance) {
const Component = instance.type as ComponentOptions
// 编译模板
if (!instance.render) {
if (compile && !Component.render) {
const template = Component.template
if (template) {
Component.render = compile(template, { ... })
}
}
instance.render = Component.render || NOOP
}
// 兼容 Options API
if (__FEATURE_OPTIONS_API__) {
setCurrentInstance(instance)
pauseTracking()
applyOptions(instance)
resetTracking()
unsetCurrentInstance()
}
}
四、渲染副作用
4.1 setupRenderEffect
const setupRenderEffect = (
instance: ComponentInternalInstance,
initialVNode: VNode,
container,
anchor,
parentSuspense,
namespace,
optimized
) => {
const componentUpdateFn = () => {
if (!instance.isMounted) {
// ========== 首次挂载 ==========
// 触发 beforeMount
if (instance.bm) {
invokeArrayFns(instance.bm)
}
// 渲染子树
const subTree = (instance.subTree = renderComponentRoot(instance))
// patch 子树
patch(null, subTree, container, anchor, instance, parentSuspense, namespace)
// 保存 el
initialVNode.el = subTree.el
// 触发 mounted
if (instance.m) {
queuePostRenderEffect(instance.m, parentSuspense)
}
instance.isMounted = true
} else {
// ========== 更新 ==========
let { next, vnode } = instance
if (next) {
next.el = vnode.el
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
// 触发 beforeUpdate
if (instance.bu) {
invokeArrayFns(instance.bu)
}
// 渲染新子树
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
// patch
patch(prevTree, nextTree, hostParentNode(prevTree.el)!, ...)
next.el = nextTree.el
// 触发 updated
if (instance.u) {
queuePostRenderEffect(instance.u, parentSuspense)
}
}
}
// 创建响应式副作用
const effect = (instance.effect = new ReactiveEffect(
componentUpdateFn,
NOOP,
() => queueJob(update) // scheduler
))
const update = (instance.update = () => {
if (effect.dirty) {
effect.run()
}
})
update.id = instance.uid
// 首次执行
update()
}
4.2 renderComponentRoot
export function renderComponentRoot(instance: ComponentInternalInstance): VNode {
const {
type: Component,
vnode,
proxy,
props,
slots,
attrs,
emit,
render,
renderCache,
data,
setupState
} = instance
let result
const prev = setCurrentRenderingInstance(instance)
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
// 有状态组件
const proxyToUse = proxy
result = normalizeVNode(
render!.call(proxyToUse, proxyToUse, renderCache, props, setupState, data)
)
} else {
// 函数组件
const render = Component as FunctionalComponent
result = normalizeVNode(render(props, { attrs, slots, emit }))
}
} catch (err) {
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
result = createVNode(Comment)
}
setCurrentRenderingInstance(prev)
return result
}
五、生命周期
5.1 生命周期钩子注册
// packages/runtime-core/src/apiLifecycle.ts
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
function createHook(lifecycle: LifecycleHooks) {
return (hook: Function, target = currentInstance) => {
injectHook(lifecycle, (...args) => hook(...args), target)
}
}
function injectHook(
type: LifecycleHooks,
hook: Function,
target: ComponentInternalInstance | null
) {
if (target) {
const hooks = target[type] || (target[type] = [])
// 包装 hook,设置当前实例
const wrappedHook = (...args: unknown[]) => {
if (target.isUnmounted) return
pauseTracking()
const reset = setCurrentInstance(target)
const res = callWithAsyncErrorHandling(hook, target, type, args)
reset()
resetTracking()
return res
}
hooks.push(wrappedHook)
return wrappedHook
}
}
5.2 生命周期执行时机
┌─────────────────────────────────────────────────────────────┐
│ 组件生命周期 │
├─────────────────────────────────────────────────────────────┤
│ │
│ setup() │
│ │ │
│ ▼ │
│ onBeforeMount() │
│ │ │
│ ▼ │
│ patch(null, subTree) ──► 子组件递归挂载 │
│ │ │
│ ▼ │
│ onMounted() ──► queuePostRenderEffect │
│ │ │
│ ▼ │
│ ─────────── 响应式数据变化 ─────────── │
│ │ │
│ ▼ │
│ onBeforeUpdate() │
│ │ │
│ ▼ │
│ patch(prevTree, nextTree) │
│ │ │
│ ▼ │
│ onUpdated() ──► queuePostRenderEffect │
│ │ │
│ ▼ │
│ ─────────── 组件卸载 ─────────── │
│ │ │
│ ▼ │
│ onBeforeUnmount() │
│ │ │
│ ▼ │
│ unmount(subTree) ──► 子组件递归卸载 │
│ │ │
│ ▼ │
│ onUnmounted() ──► queuePostRenderEffect │
│ │
└─────────────────────────────────────────────────────────────┘
六、Props 处理
6.1 initProps
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number
) {
const props: Data = {}
const attrs: Data = {}
setFullProps(instance, rawProps, props, attrs)
// 验证 props
if (__DEV__) {
validateProps(rawProps || {}, props, instance)
}
if (isStateful) {
// 有状态组件,props 需要响应式
instance.props = shallowReactive(props)
} else {
// 函数组件
instance.props = instance.type.props ? props : attrs
}
instance.attrs = attrs
}
6.2 updateProps
export function updateProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
rawPrevProps: Data | null,
optimized: boolean
) {
const { props, attrs } = instance
const rawCurrentProps = toRaw(props)
setFullProps(instance, rawProps, props, attrs)
// 删除不存在的 props
for (const key in rawCurrentProps) {
if (!rawProps || !hasOwn(rawProps, key)) {
delete props[key]
}
}
}
七、Slots 处理
export function initSlots(
instance: ComponentInternalInstance,
children: VNodeNormalizedChildren
) {
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
const type = (children as RawSlots)._
if (type) {
instance.slots = toRaw(children as InternalSlots)
def(children as InternalSlots, '_', type, true)
} else {
normalizeObjectSlots(children as RawSlots, (instance.slots = {}))
}
} else {
instance.slots = {}
if (children) {
normalizeVNodeSlots(instance, children)
}
}
}
八、调试技巧
8.1 关键断点
// 组件挂载
packages/runtime-core/src/renderer.ts → mountComponent
// setup 执行
packages/runtime-core/src/component.ts → setupStatefulComponent
// 渲染副作用
packages/runtime-core/src/renderer.ts → setupRenderEffect
8.2 查看组件实例
// 在 setup 中
import { getCurrentInstance } from 'vue'
setup() {
const instance = getCurrentInstance()
console.log(instance)
}
九、小结
Vue3 组件系统的核心:
- 组件实例:ComponentInternalInstance 管理组件状态
- setup 执行:在 beforeCreate 之前执行,返回状态或 render
- 渲染副作用:ReactiveEffect 自动追踪依赖,响应式更新
- 生命周期:钩子数组,按顺序执行
- Props/Slots:初始化和更新机制
📦 源码地址:github.com/vuejs/core
下一篇:编译器原理
如果觉得有帮助,欢迎点赞收藏 👍