「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
Composition API中使用Provide / Inject
Provide / Inject 使用场景
通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。对于这种情况,我们可以使用一对
provide和inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这些数据。
在SFC页面 setup 中使用 Provide / Inject
- 例如有这样层次的一个组件
Root
└─ Parent
└─ Son
└─ GrandSon
- 父组建
./Parent.vue,provide提供一个msg的响应式数据,如果要确保通过provide传递的数据不会被inject的组件更改,可以使用readonly
<template>
<Son />
</template>
<script>
import { ref, provide,readonly } from 'vue'
import Son from './son.vue'
export default {
components: {
Son
},
setup() {
const msg = ref('I'm from parent')
provide('msg', readonly(msg))
}
}
</script>
- 儿子组件
./Son.vue,又使用了一个组件 GrandSon
<template>
<GrandSon />
</template>
<script>
import GrandSon from './MyMarker.vue'
export default {
components: {
GrandSon
}
}
</script>
- 孙子组件
./GrandSon.vue,inject接收从上层传入的msg数据,当上层的数据发生变化,这里也会响应做出变化
<script>
import { inject } from 'vue'
export default {
setup() {
const userParent = inject('msg')
return {
userParent
}
}
}
</script>
源码解析
provide,默认情况下,实例继承父类的provides对象,如果当前组件有自己的provide,它使用父provides对象作为原型来创建自己的provides对象,在inject只需要查询原型链就行
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
if (!currentInstance) {
...
} 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
}
}
inject, 查询父级的的provides,如果实例是在根目录,回退到appContext的provides中
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
if (instance) {
// #2400
// to support `app.use` plugins,
// fallback to appContext's `provides` if the intance is at root
const provides =
instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides
: instance.parent.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.proxy)
: defaultValue
} else if (__DEV__) {
...
}
} else if (__DEV__) {
...
}
}