Vue.js 源码揭秘(四):组件系统

18 阅读4分钟

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 组件系统的核心:

  1. 组件实例:ComponentInternalInstance 管理组件状态
  2. setup 执行:在 beforeCreate 之前执行,返回状态或 render
  3. 渲染副作用:ReactiveEffect 自动追踪依赖,响应式更新
  4. 生命周期:钩子数组,按顺序执行
  5. Props/Slots:初始化和更新机制

📦 源码地址:github.com/vuejs/core

下一篇:编译器原理

如果觉得有帮助,欢迎点赞收藏 👍