provide和inject是什么?
inject()
注入一个由祖先组件或整个应用 (通过
app.provide()
) 提供的值。
第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject()
将返回undefined
,除非提供了一个默认值。
provide()
提供一个值,可以被后代组件注入。
总得来说provide
和 inject
主要解决的是跨级组件间的通信问题
。
使用示例:
import { provide, inject, h } from 'vue';
const ThemeSymbol = Symbol();
const Provider = {
setup() {
provide(ThemeSymbol, 'dark');
},
render() {
return this.$slots.default();
}
}
const Consumer = {
setup() {
const theme = inject(ThemeSymbol);
return { theme };
},
render() {
return h('div', `Theme: ${this.theme}`);
}
}
const App = {
components: {
Provider,
Consumer
},
template: `
<Provider>
<Consumer></Consumer>
</Provider>
`
}
createApp(App).mount('#app');
源码实现
github1s链接: github1s.com/vuejs/core/…
先看provide
源码:
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
const parentProvides =
currentInstance.parent && currentInstance.parent.provides
if (parentProvides === provides) {
provides = currentInstance.provides = Object.create(parentProvides)
}
provides[key as string] = value
}
}
从当前组件实例找到provides
。找当前组件实例父的provides
。
如果他们是相等的。那么创建一个原型指向父provides
的对象作为组件实例的provides
。
(创建一个新对象是为了维护组件自己的provides
)
(利用原型链的原理,查找属性的时候如果provides对象没有属性。会沿着原型链找。那么就会找到父组件的provides。不断向上查找。)
注意:
每个组件实例都会有一个 provides
属性,这个属性在组件实例创建时就会被初始化
在初始化过程中,如果一个组件没有声明 provide
,那么它的 provides 对象会被设置为其父组件的 provides 对象
这个设计使得 inject
可以跨越任何层级的组件来获取值,而无需担心中间组件是否有 provide
。
下面这行源码才能解释,为什么可以跨组件传递和获取值?
因为组件初始化就根据组件链构建了provides链
。
创建组件(createComponentInstance
)的时候provides属性已经默认赋值了父组件的provides
。
再看inject
源码:
export function inject(
key: InjectionKey<any> | string,
defaultValue?: unknown,
treatDefaultAsFactory = false,
) {
const instance = currentInstance || currentRenderingInstance
if (instance || currentApp) {
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) {
return provides[key as string]
} else if (arguments.length > 1) {
return treatDefaultAsFactory && isFunction(defaultValue)
? defaultValue.call(instance && instance.proxy)
: defaultValue
}
}
}
获取当前组件实例的父组件的provides。并且provides[key]。这时候会沿着provide时候构建的原型链查找并返回。
总结:
明白了组件的provides本质上是利用js的原型链实现
这个原理后。
下次面试官问:
如果A -> B -> C 3个组件组件是一个嵌套结构。
那么A和B都provide了一个 foo。
C组件inject('fod')的时候。获取的是A还是B的foo?
就知道 inject
本质上是在根据(组件初始化时候)构建的provides链上查找。找到最近一个满足条件(存在查找的key)的上级的provide
的属性。