#前言
首先我们由一个问题引入(假定读者了解了Vue父子组件通信的方法---prop/emit),我们已经知道了父子通信,那么祖孙通信、太爷和重孙的通信,我们该如何实现呢?哈哈很形象的比喻。
就是说Vue组件可以是一个多层级嵌套的组件(<GrandFather />组件中使用了<Father />组件,而在father.Vue单文件组件中,又使用了<Son />组件,这就构成了组件树),我们现在需要实现<GrandFather />和<Son />之间的通信,其实我们可以用Props方法让prop从<GrandFather />逐级向下传递,但是这样会很麻烦,有时候<Father />组件根本不需要<GrandFather />传过来的属性,这样的花就给我自己添麻烦了。(祖孙间的悄悄话,为什么要让父亲知道呢?)
这时候,Vue就提供给了我们依赖注入的方法(provide/inject)。一个父组件相对于其所有的后代组件,会作为依赖提供者(父组件提供给后代子组件需要的数据)。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
#Provide (提供)
要为组件后代提供数据,需要使用到 provide 选项。
对于 provide
对象上的每一个属性,后代组件会用其 key 为注入名(给注入到后代组件中的数据起了个名字)查找期望注入的值,属性的值就是要提供的数据。
export default {
provide: {
message: 'hello!'
}
}
如果我们需要提供依赖当前组件实例的状态 (比如那些由 data()
定义的数据属性),那么可以以函数形式使用 provide。
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
// 使用函数的形式,可以访问到 `this`
return {
message: this.message
}
}
}
然而,请注意这不会使注入保持响应性!!!后续将会讨论如何将注入转变为响应式。
#应用层 Provide
除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖,这样我们就可以为任何一个组件注入我们在应用层面提供的依赖(数据)。
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
这在你编写插件特别有用,因为插件一般都不会使用组件形式来提供值。
#Inject (注入)
要注入上层组件提供的数据,需使用 inject 选项来声明
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}
注入时机:注入会在组件自身的状态之前被解析,因此你可以在 data()
中访问到注入的属性
export default {
inject: ['message'],
data() {
return {
// 基于注入值的初始数据
fullMessage: this.message
}
}
}
那么我们思考一个小问题吧。
我们provide的时候,可以用key定义注入名(比如key为filmList),但是我们需要这个数据的子组件,自身的data()中已经有一个filmList的状态了,那我们就可以在注入的时候起别名了!
在接受方式上,我们不在向之前那样用字符串数组的形式接收,而采用了对象 { } 形式。
export default {
inject: {
/* 本地属性名 */ list: {
from: /* 注入来源名 */ 'filmList'
}
}
}
这里,组件本地化了原注入名 "filmList"
所提供的属性,并将其暴露为 this.list。
#注入默认值
默认情况下,inject
假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。
如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似:
export default {
// 当声明注入的默认值时
// 必须使用对象形式
inject: {
message: {
from: 'message', // 当与原注入名同名时,这个属性是可选的
default: 'default value'
},
user: {
// 对于非基础类型数据,如果创建开销比较大,或是需要确保每个组件实例
// 需要独立数据的,请使用工厂函数
default: () => ({ name: 'John' })
}
}
}
#和响应式数据配合使用
前面我们所学的 provide 和 inject 都不具备响应性,为保证注入方和供给方之间的响应性链接,我们需要使用 computed() 函数提供一个计算属性。
import { computed } from 'vue'
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
return {
// 显式提供一个计算属性
message: computed(() => this.message)
}
}
}
!!!临时配置要求!!!
上面的用例需要设置
app.config.unwrapInjectedRef = true
以保证注入会自动解包这个计算属性。这将会在 Vue 3.3 后成为一个默认行为,而我们暂时在此告知此项配置以避免后续升级对代码的破坏性。在 3.3 后就不需要这样做了。
#使用 Symbol (独一无二的值)作注入名
至此,我们已经了解了如何使用字符串作为注入名。但如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol (ES6语法)来作为注入名以避免潜在的冲突。
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { myInjectionKey } from './keys.js'
export default {
provide() {
return {
[myInjectionKey]: {
/* 要提供的数据 */
}
}
}
}
// 注入方组件
import { myInjectionKey } from './keys.js'
export default {
inject: {
localName: { from: myInjectionKey }
}
}
为了更美好的明天而战!!!