💡 [本系列Vue3源码解读文章基于3.3.4版本](https://github.com/vuejs/core/tree/v3.3.4) 欢迎关注公众号:《前端 Talkking》
1、前言
在 Vuejs 中,我们可以使用 Provide
和 Inject
方法来实现跨父子组件的通信,这样一来,无论组件嵌套多少层级,都可以在后代组件中访问它们祖先组件的数据。
2、源码实现
Provide API
provide
的作用是提供数据,我们来看它的源码实现: provide API 源码实现
typescript
export function provide<T, K = InjectionKey<T> | string | number>(
key: K,
value: K extends InjectionKey<infer V> ? V : T
) {
if (!currentInstance) {
if (__DEV__) {
warn(`provide() can only be used inside setup().`)
}
} else {
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.
const parentProvides =
currentInstance.parent && currentInstance.parent.provides
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
// TS doesn't allow symbol as index type
provides[key as string] = value
}
}
根据以上代码可知:
- 当组件实例调用
provide
函数的时候,它会调用父级provides
对象作为原型对象创建自己的provides
对象,然后再给自己的provides
添加新的属性值; provide
提供的数据保存在组件的provides
对象上。
我们来看看创建组件实例的时候,是怎么把 provide
放到组件实例上的: 组件实例对象源码
typescript
const instance: ComponentInternalInstance = {
// 省略部分代码
provides: parent ? parent.provides : Object.create(appContext.provides),
}
由此可见,默认情况下,组件实例的 provides
对象指向其父组件的 provides
对象,因此会形成以下调用关系:
分析完 provide
实现原理后,接下来我们来看看 inject
的实现原理。
Inject API
inject
的作用是注入数据,该数据来自于它的祖先组件 provide
方法提供的数据,我们来看它的源码实现: inject API 源码实现
typescript
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false
) {
// fallback to `currentRenderingInstance` so that this can be called in
// a functional component
const instance = currentInstance || currentRenderingInstance
// 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
const provides = instance
? instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.provides
: currentApp!._context.provides
if (provides && (key as string | symbol) in provides) {
// TS doesn't allow symbol as index type
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance && instance.proxy)
: defaultValue
} else if (__DEV__) {
warn(`injection "${String(key)}" not found.`)
}
} else if (__DEV__) {
warn(`inject() can only be used inside setup() or functional components.`)
}
}
如果在一个组件中使用 inject(key, 'a')
方法,那么它会先从其父组件的 provides
对象本身去查找这个 key
,如果找到了就返回对应的数据,如果没有找到,则通过 provides
的原型去查找这个 key
,此时的 provides
的原型指向的就是它的父级 provides
对象。实际上,inject
查找数据的方法其实就是利用了 JavaScript
中原型链查找方式。
3、总结
Vue 可以使用 provide
和 inject
实现跨组件数据传递,其中:
provide
提供的数据保存在组件的provides
对象上,创建provides
的时候,将父组件的provide
作为自己的原型,因此会形成原型链;inject
使用数据时,会沿着provides
的原型上去查找这个key
,本质是利用了JavaScript
中原型链查找方式;
4、参考资料
[1]vue官网
[2]vuejs设计与实现
[3]vue3源码