vue 3.x 关于 provide 与 inject 实现原理

6 阅读4分钟

vue@3.5.29

provide/inject 是 Vue3 中实现跨组件(任意层级)通信的核心 API。

  1. provide:父组件 / 应用实例通过该 API 向下「提供」数据,可被所有后代组件访问;
  2. inject:后代组件通过该 API 「注入」祖先 / 应用提供的数据,无需逐层传递 props;
  3. 解决的问题:
    • 替代「props 透传」(多层组件传递 props 繁琐、代码冗余);
    • 实现跨任意层级组件的通信(如祖孙组件、全局应用级数据共享)。

provide

provide 通过父组件 / 应用实例通过该 API 向下「提供」数据,可被所有后代组件访问。

/**
 * provide 是 Vue3 组合式 API 中依赖注入的「提供方」核心函数,
 * 用于在父组件 / 祖先组件中定义可被后代组件通过 inject 读取的响应式数据 / 方法,
 * 实现跨组件层级的数据传递(无需逐层 props 透传)。
 * @param key 注入键
 * @param value 注入值
 */
export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T,
): void {
  if (__DEV__) {
    // / 无当前组件实例 或 组件已挂载 → 抛出警告
    if (!currentInstance || currentInstance.isMounted) {
      warn(`provide() can only be used inside setup().`)
    }
  }
  if (currentInstance) {
    // 获取当前组件的 provides 对象(默认继承父组件)
    let provides = currentInstance.provides
    // by default an instance inherits its parent's provides object
    // but when it needs to provide values of its own, it creates its
    // own provides object using parent provides object as prototype.
    // this way in `inject` we can simply look up injections from direct
    // parent and let the prototype chain do the work.
    // 获取父组件的 provides 对象
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides

    // 若当前 provides 与父 provides 指向同一对象(未初始化过)
    if (parentProvides === provides) {
      // 创建新的 provides 对象,原型链指向父 provides → 继承父的注入值
      provides = currentInstance.provides = Object.create(parentProvides)
    }
    // TS doesn't allow symbol as index type
    provides[key as string] = value
  }
}

每个组件的 provides 对象通过 Object.create(parent.provides) 创建,因此原型链指向父组件 provides,实现「向上查找」。

let currentInstance: ComponentInternalInstance | null = null
/**
 * 组件内部实例
 * 每个 Vue 组件在运行时都会对应一个 ComponentInternalInstance 实例,Vue 内部通过这个实例掌控组件的一切行为。
 * We expose a subset of properties on the internal instance as they are
 * useful for advanced external libraries and tools.
 */
export interface ComponentInternalInstance {
  uid: number // 组件唯一ID(全局递增,用于区分不同组件实例)
  type: ConcreteComponent // 组件类型(如用户定义的 { setup(), template: ... })
  parent: ComponentInternalInstance | null // 父组件实例(根组件为 null)
  root: ComponentInternalInstance // 根组件实例(所有组件都指向根,方便快速访问)
  appContext: AppContext // 组件所属的应用上下文(包含 app.config、全局组件/指令等)
  /**
   * 组件在父 VNode 树中的节点(父组件眼中的当前组件)
   * Vnode representing this component in its parent's vdom tree
   */
  vnode: VNode
  /**
   * 父组件更新时的「待生效新 VNode」(更新阶段临时存储)
   * The pending new vnode from parent updates
   * @internal
   */
  next: VNode | null
  /**
   *  组件自身渲染出的「根 VNode 子树」(即组件内部的 DOM/子组件)
   * Root vnode of this component's own vdom tree
   */
  subTree: VNode
  /**
   * 组件的渲染副作用(响应式数据变化时触发重新渲染)
   * Render effect instance
   */
  effect: ReactiveEffect
  /**
   * 强制更新函数(对应 $forceUpdate,手动触发渲染)
   * Force update render effect
   */
  update: () => void
  /**
   * 渲染任务(交给调度器的函数,包含「脏检查」逻辑)
   * Render effect job to be passed to scheduler (checks if dirty)
   */
  job: SchedulerJob
  /**
   * 组件的渲染函数(编译 template 生成)
   * The render function that returns vdom tree.
   * @internal
   */
  render: InternalRenderFunction | null
  /**
   * SSR 专用渲染函数
   * SSR render function
   * @internal
   */
  ssrRender?: Function | null
  /**
   * Object containing values this component provides for its descendants
   * @internal
   */
  provides: Data
  /**
   * for tracking useId()
   * first element is the current boundary prefix
   * second number is the index of the useId call within that boundary
   * @internal
   */
  ids: [string, number, number]
  /**
   * Tracking reactive effects (e.g. watchers) associated with this component
   * so that they can be automatically stopped on component unmount
   * @internal
   */
  scope: EffectScope
  /**
   *  代理访问类型缓存(避免频繁 hasOwnProperty 检查)
   * cache for proxy access type to avoid hasOwnProperty calls
   * @internal
   */
  accessCache: Data | null
  /**
   * 渲染函数缓存(如内联事件处理器)
   * cache for render function values that rely on _ctx but won't need updates
   * after initialized (e.g. inline handlers)
   * @internal
   */
  renderCache: (Function | VNode | undefined)[]

  /**
   * 解析后的组件注册表(mixins/extends 场景)
   * Resolved component registry, only for components with mixins or extends
   * @internal
   */
  components: Record<string, ConcreteComponent> | null
  /**
   * 解析后的指令注册表(mixins/extends 场景)
   * Resolved directive registry, only for components with mixins or extends
   * @internal
   */
  directives: Record<string, Directive> | null
  /**
   * 过滤器(仅兼容 Vue2)
   * Resolved filters registry, v2 compat only
   * @internal
   */
  filters?: Record<string, Function>
  /**
   * resolved props options
   * @internal
   */
  propsOptions: NormalizedPropsOptions
  /**
   * resolved emits options
   * @internal
   */
  emitsOptions: ObjectEmitsOptions | null
  /**
   * resolved inheritAttrs options
   * @internal
   */
  inheritAttrs?: boolean
  /**
   * 自定义元素实例
   * Custom Element instance (if component is created by defineCustomElement)
   * @internal
   */
  ce?: ComponentCustomElementInterface
  /**
   * 是否是自定义元素
   * is custom element? (kept only for compatibility)
   * @internal
   */
  isCE?: boolean
  /**
   * 自定义元素 HMR 方法
   * custom element specific HMR method
   * @internal
   */
  ceReload?: (newStyles?: string[]) => void

  // the rest are only for stateful components ---------------------------------

  // main proxy that serves as the public instance (`this`)
  // 组件公共实例(用户代码中的 `this`)
  proxy: ComponentPublicInstance | null

  // exposed properties via expose()
  exposed: Record<string, any> | null // 通过 expose() 暴露的属性
  exposeProxy: Record<string, any> | null // 暴露属性的代理(限制用户访问范围)

  /**
   * 运行时编译 render 函数的专用 proxy
   * alternative proxy used only for runtime-compiled render functions using
   * `with` block
   * @internal
   */
  withProxy: ComponentPublicInstance | null
  /**
   * 公共实例的目标对象(存储 computed、methods、自定义属性)
   * This is the target for the public instance proxy. It also holds properties
   * injected by user options (computed, methods etc.) and user-attached
   * custom properties (via `this.x = ...`)
   * @internal
   */
  ctx: Data

  // state
  data: Data // 组件的 data 数据(响应式)
  props: Data // 解析后的 props 数据(响应式)
  attrs: Data // 组件的 attrs(非 props 的属性,如 class、style)
  slots: InternalSlots // 组件的插槽(编译后的内部格式)
  refs: Data // 组件的 refs 集合($refs)
  emit: EmitFn // 组件的 emit 方法(触发自定义事件)

  /**
   * 跟踪 .once 事件是否已触发
   * used for keeping track of .once event handlers on components
   * @internal
   */
  emitted: Record<string, boolean> | null
  /**
   * props 默认值缓存(避免默认工厂函数重复执行触发 watcher)
   * used for caching the value returned from props default factory functions to
   * avoid unnecessary watcher trigger
   * @internal
   */
  propsDefaults: Data
  /**
   * setup 函数返回的状态(响应式)
   * setup related
   * @internal
   */
  setupState: Data
  /**
   * devtools access to additional info
   * @internal
   */
  devtoolsRawSetupState?: any
  /**
   * setup 函数的上下文(emit、slots、attrs 等)
   * @internal
   */
  setupContext: SetupContext | null

  /**
   * 所属的 Suspense 边界实例
   * suspense related
   * @internal
   */
  suspense: SuspenseBoundary | null
  /**
   * Suspense 挂起批次 ID
   * suspense pending batch id
   * @internal
   */
  suspenseId: number
  /**
   * 异步依赖(如异步组件的 Promise)
   * @internal
   */
  asyncDep: Promise<any> | null
  /**
   * 异步依赖是否已解析
   * @internal
   */
  asyncResolved: boolean

  // lifecycle
  isMounted: boolean // 是否已挂载
  isUnmounted: boolean // 是否已卸载
  isDeactivated: boolean // 是否被 KeepAlive 失活(隐藏)
  /**
   * @internal
   */
  [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.CREATED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.MOUNTED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.UPDATED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.UNMOUNTED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.ACTIVATED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.DEACTIVATED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
  /**
   * @internal
   */
  [LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise<unknown>>

  /**
   *  缓存 $forceUpdate 方法(避免每次访问都绑定 this)
   * For caching bound $forceUpdate on public proxy access
   * @internal
   */
  f?: () => void
  /**
   * 缓存 $nextTick 方法
   * For caching bound $nextTick on public proxy access
   * @internal
   */
  n?: () => Promise<void>
  /**
   * 更新 Teleport 组件的 CSS 变量
   * `updateTeleportCssVars`
   * For updating css vars on contained teleports
   * @internal
   */
  ut?: (vars?: Record<string, unknown>) => void

  /**
   * 开发环境:样式 v-bind 水合校验
   * dev only. For style v-bind hydration mismatch checks
   * @internal
   */
  getCssVars?: () => Record<string, unknown>

  /**
   * 合并后的 Vue2 风格选项
   * v2 compat only, for caching mutated $options
   * @internal
   */
  resolvedOptions?: MergedComponentOptions
}

inject

inject 可以在后代组件通过该 API 注入祖先 / 应用提供的数据,无需逐层传递 props。

// 重载 1:仅传入 key(无默认值)/
export function inject<T>(key: InjectionKey<T> | string): T | undefined

// 重载 2:传入 key + 普通默认值
export function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: T,
  treatDefaultAsFactory?: false,
): T
// 重载 3:传入 key + 工厂函数默认值
export function inject<T>(
  key: InjectionKey<T> | string,
  defaultValue: T | (() => T),
  treatDefaultAsFactory: true,
): T
export function inject(
  key: InjectionKey<any> | string,
  defaultValue?: unknown,
  treatDefaultAsFactory = false) {
  // fallback to `currentRenderingInstance` so that this can be called in
  // a functional component
  // 获取当前组件实例(兼容函数式组件,回退到 currentRenderingInstance)
  const instance = getCurrentInstance()

  // also support looking up from app-level provides w/ `app.runWithContext()`
  // 仅当存在组件实例/应用上下文时执行核心逻辑
  if (instance || currentApp) {
    // #2400
    // to support `app.use` plugins,
    // fallback to appContext's `provides` if the instance is at root
    // #11488, in a nested createApp, prioritize using the provides from currentApp
    // #13212, for custom elements we must get injected values from its appContext
    // as it already inherits the provides object from the parent element
    // 确定要查找的 provides 对象
    let provides = currentApp
      ? currentApp._context.provides // 场景1:有应用上下文 → 用应用级 provides
      : instance
        ? instance.parent == null || instance.ce // 场景2:组件是根组件/自定义元素
        // 从 vnode 的 appContext 取 provides
          ? instance.vnode.appContext && instance.vnode.appContext.provides
          : instance.parent.provides // 普通组件 → 从父组件 provides 开始查找
        : undefined

    // 查找 key 并返回结果
    if (provides && (key as string | symbol) in provides) {
      // TS doesn't allow symbol as index type
      return provides[key as string]

      // 未找到 key,处理默认值(参数个数 > 1 表示传了 defaultValue)
    } else if (arguments.length > 1) {
      return treatDefaultAsFactory && isFunction(defaultValue)
        ? defaultValue.call(instance && instance.proxy)
        : defaultValue
        // 未找到 key 且无默认值 → 开发环境警告
    } else if (__DEV__) {
      warn(`injection "${String(key)}" not found.`)
    }
    // 无组件实例/应用上下文 → 开发环境警告(调用时机错误)
  } else if (__DEV__) {
    warn(`inject() can only be used inside setup() or functional components.`)
  }
}

根组件 / 自定义元素会优先查找 appContext.provides(应用级注入)。