Vue3 的 provide 与 inject 实现原理
描述
provide 与 inject 是 Vue3 中实现组件跨层级数据传递的 API。它们允许祖先组件提供数据,后代组件无论层级多深都能注入使用这些数据,避免了 props 的逐层传递。
实现原理详解
1. 组件实例的数据结构设计
每个组件实例内部维护两个关键属性:
provides:存储当前组件提供的数据parent:指向父组件实例
interface ComponentInternalInstance {
provides: Record<string, any>
parent: ComponentInternalInstance | null
// ... 其他属性
}
2. 初始化组件实例时的 provides 处理
当创建组件实例时,会继承父组件的 provides 对象:
function createComponentInstance() {
const instance = {
provides: parent ? Object.create(parent.provides) : Object.create(null),
parent,
// ... 其他初始化
}
return instance
}
这里使用 Object.create(parent.provides) 实现原型链继承,这样当前组件的 provides 对象可以通过原型链访问父级提供的数据。
3. provide 的实现原理
provide 函数将数据存储到当前组件实例的 provides 对象中:
function provide(key, value) {
const currentInstance = getCurrentInstance()
if (currentInstance) {
let provides = currentInstance.provides
// 如果当前组件的 provides 与父级相同,说明是第一次调用 provide
// 需要重新创建新的 provides 对象,避免污染父级
if (currentInstance.parent?.provides === provides) {
provides = currentInstance.provides = Object.create(provides)
}
provides[key] = value
}
}
关键点:第一次调用 provide 时会创建新的 provides 对象,这样修改不会影响父级。
4. inject 的实现原理
inject 函数沿着组件链向上查找提供的数据:
function inject(key, defaultValue) {
const currentInstance = getCurrentInstance()
if (currentInstance) {
const provides = currentInstance.parent?.provides
if (provides && key in provides) {
return provides[key]
} else if (arguments.length > 1) {
return defaultValue
} else {
// 开发环境下警告未找到
warn(`Injection key "${key}" not found`)
}
}
}
查找过程实际上是 JavaScript 原型链的查找:
- 在当前组件的 provides 中查找
- 如果没有,通过原型链在父级 provides 中查找
- 继续向上直到根组件
5. 响应式数据的处理
当提供响应式数据时,注入的组件能够保持响应性:
// 提供响应式数据
const count = ref(0)
provide('count', count)
// 注入后仍然保持响应性
const injectedCount = inject('count')
这是因为 provide/inject 只是传递引用,响应式数据本身的响应性机制保持不变。
6. 符号(Symbol)键名的支持
为了避免命名冲突,Vue3 推荐使用 Symbol 作为提供数据的键名:
const MyKey = Symbol()
provide(MyKey, 'some value')
const value = inject(MyKey)
7. 默认值处理
inject 支持默认值,当找不到对应的提供值时使用默认值:
const value = inject('non-existent-key', 'default value')
总结
provide/inject 的实现核心是利用了 JavaScript 的原型链机制,通过组件树的层级关系建立了一条数据提供链。这种设计既保证了数据的跨层级传递,又保持了组件树的清晰结构,是 Vue3 组件通信的重要机制。