组件的provide/inject
阅读本篇之前应当先阅读组件基础。
通常当我们需要将数据从父组件传递到子组件时,我们通过props实现。 设想一下这样的一个组件结构,这个结构中有一些嵌套层级很深的组件,我们想在内嵌层级很深的一个子组件中,调用顶层父组件的某些数据。在这种情况下,如果我们仍需要通过prop从父组件到子组件之间的组件链中传递数据,这可能挺烦人的。
为了应对这种情况,我们可以使用provide与inject的组合。父组件可以无视组件层级的深度,作为所有子组件的依赖提供者。这个特性基于两部分:父组件通过provide选项提供数据,并且子组件通过inject选项使用所提供的数据。
例如像这样的一个层级:
Root
└─ TodoList
├─ TodoItem
└─ TodoListFooter
├─ ClearTodosButton
└─ TodoListStatistics
如果我们想将todo-items的长度传递到TodoListStatistics,我们可能要通过prop,按照TodoList --> TodoListFooter --> TodoListStatistics的层级,一层层地往下传。但通过provide / inject,我们可以直接这样实现:
const app = Vue.createApp({})
app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide: {
user: 'John Doe'
},
template: `
<div>
{{ todos.length }}
<!-- rest of the template -->
</div>
`
})
app.component('todo-list-statistics', {
inject: ['user'],
created() {
console.log(`Injected property: ${this.user}`) // > Injected property: John Doe
}
})
然而,如果我们尝试在这提供一些组件实例property的话,它们将不能正常工作:
app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide: {
todoLength: this.todos.length // this will result in error 'Cannot read property 'length' of undefined`
},
template: `
...
`
})
为了能够访问组件实例property,我们需要将provide转换成为返回一个对象的方法:
app.component('todo-list', {
data() {
return {
todos: ['Feed a cat', 'Buy tickets']
}
},
provide() {
return {
todoLength: this.todos.length
}
},
template: `
...
`
})
这样,我们可以更安全地继续开发这个组件,而不用当心改变或移除了某些子组件可能依赖的东西。组件之间的接口仍保持着清晰的定义,就像props一样。
事实上,只要你不需要关心以下两种情况,你可以将这种依赖注入当作“作用范围广的props”:
- 父组件无需知道哪个孙子用了自己提供的property
- 子组件无需知道所使用的注入property来自哪个小祖宗。
与响应性配合使用
在上面的例子中,如果我们改变了todos的列表,所作出的改动是不会反映到todoLength这个被被注入的property的。这是因为provide / inject之间的绑定默认不是响应式的。为了改变这种行为,可以将一个ref property或reactive对象传递到provide之中。在我们的例子中,如果我们想对祖宗组件的变动作出响应,我们需要分配一个Composition API计算属性给我们提供的todoLength:
app.component('todo-list', {
// ...
provide() {
return {
todoLength: Vue.computed(() => this.todos.length)
}
}
})
这样,任何对todos.length的改动,在todoLength被注入的组件中都会正确地反映。如果想了解更多,可以阅读Composition API章节中的响应式的provide / inject。