Skip to content

第 14 章 依赖注入与插件系统

本章要点

  • provide/inject 的本质:基于原型链的依赖传递机制
  • 依赖注入的解析过程:从当前组件沿 parent 链向上查找
  • InjectionKey 的类型安全设计:Symbol + 泛型的巧妙结合
  • app.provide 的全局注入:应用级别的依赖如何注入到每一个组件
  • 插件系统的设计哲学:app.use() 如何组织第三方扩展
  • 插件的安装机制:install 函数与重复安装检测
  • 依赖注入在大型应用中的架构价值:替代 props drilling 的优雅方案

依赖注入是 Vue 中最容易被低估的特性之一。很多开发者只在"跨层级传递数据"时才想到 provide/inject,却忽略了它在架构层面的深远意义——它是 Vue 插件系统的基石,是组合式函数共享状态的关键通道,也是 Pinia、Vue Router 等官方库与组件树交互的核心机制。

在前面的章节中,我们深入了解了组件系统的实例化过程和生命周期。本章将揭开组件间数据流动的另一条路径——不是自上而下的 props,也不是自下而上的 emit,而是"穿越"组件层级的依赖注入。

14.1 provide/inject 的基本模型

从问题出发

考虑一个典型的场景:一个主题系统需要将主题配置从根组件传递到任意深度的子组件。

typescript
// 使用 props drilling —— 痛苦的方式
// App → Layout → Sidebar → Menu → MenuItem → Icon
// 每一层都要声明和传递 theme prop

provide/inject 提供了更优雅的方案:

typescript
// 祖先组件
const app = {
  setup() {
    const theme = reactive({ mode: 'dark', primary: '#42b883' })
    provide('theme', theme)
  }
}

// 任意深度的后代组件
const DeepChild = {
  setup() {
    const theme = inject('theme')
    // 直接使用,无需中间组件传递
  }
}

看起来像是"魔法"——数据怎么就"穿越"了中间的组件层级?答案藏在 JavaScript 最基础的机制中。

原型链:依赖注入的底层引擎

Vue 的 provide/inject 底层使用了原型链继承。每个组件实例都有一个 provides 对象,子组件的 provides 的原型指向父组件的 provides

MenuItem 调用 inject('theme') 时,JavaScript 引擎沿原型链向上查找,天然地实现了"跨层级查找"。这个设计的精妙之处在于:

  1. 查找是 O(1) 到 O(n) 的:n 是组件嵌套深度,但实际上 JavaScript 引擎对原型链查找做了高度优化
  2. 中间组件零开销:不需要声明 props,不需要转发数据
  3. 同名覆盖:中间组件可以 provide 同名 key,自然地"遮蔽"祖先的值——就像作用域链中的变量遮蔽

14.2 provide 的实现

源码解析

typescript
// packages/runtime-core/src/apiInject.ts
export function provide<T, K = InjectionKey<T> | string | number>(
  key: K,
  value: K extends InjectionKey<infer V> ? V : T
): void {
  if (!currentInstance) {
    if (__DEV__) {
      warn(`provide() can only be used inside setup().`)
    }
  } else {
    let provides = currentInstance.provides

    // 关键:检查当前组件是否已经"分叉"了 provides 对象
    const parentProvides =
      currentInstance.parent && currentInstance.parent.provides

    if (parentProvides === provides) {
      // 第一次在当前组件调用 provide 时
      // 创建一个以父组件 provides 为原型的新对象
      provides = currentInstance.provides = Object.create(parentProvides)
    }

    provides[key as string] = value
  }
}

基于 VitePress 构建