Vue.js - 使用Provide/Inject实现跨层级组件通信教程(祖孙组件的数据传递)
Provide / Inject 是 Vue.js 内置的依赖注入(dependency injection)机制,用来让祖先组件向任意后代组件提供数据或方法,避免把数据层层通过 props 传递(prop drilling)。下面我将通过样例进行演示。

(2)子组件 B.vue 比较简单,主要负责导入、注册和使用孙子组件。
(3)孙组件 C 使用 inject 属性接收父组件传递的 fruit、count 属性,然后将接收到的数据绑定到 template 中进行显示。
(4)运行效果如下图所示,可以看到数据传输成功。
(2)运行结果如下,可以看到当 A 组件修改了 fruit 后,孙子组件注入的 fruit 也会跟着变化。由此可知,provide 提供的 fruit 属性是响应式数据。 也就是说后代拿到的是该对象的引用。
(2)运行结果如下,可以看到虽然父组件 A 将 count 值变为了 4,但孙组件拿到的值仍然是 3。
(2)运行结果如下,可以看到当 A.vue 组件修改了“count”后,孙子组件注入的 count 属性也会相应地改变。
1,基本介绍
(1)Provide 和 Inject 用于非父子组件之间共享数据。例如,在深度嵌套的组件中,子组件需要取父组件的部分内容。在这种情况下,如果仍然沿着组件链逐级传递 props,则会非常麻烦针对这种非父子组件之间的情况,我们可以使用 Provide 和 Inject 实现数据共享。

(2)可以看到,在父组件中使用 Provide 提供内容,在孙子组件中使用 Iniect 注入祖父提供的内容,好处如下:
- 无论层级结构多深,父组件都可以作为其所有子组件和孙子组件的依赖提供者。
- 父组件不需要知道哪些子组件使用它提供(Provide)的属性(Property)。
- 子组件不需要知道注入(Inject)的属性来自哪里。
2,基本用法
(1)假设我们有 A.vue、B.vue、C.vue 三个组件,分别代表父组件、子组件、孙子组件。其中父组件 A 主要负责导入、注册和使用 B.vue 子组件,同时使用 provide 属性向子组件和孙子组件共享 fruit、count 数据。这样子组件和孙子组件就可以使用 inject 属性实现注入和使用。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是父组件A
<B></B>
</div>
</template>
<script>
import { number } from 'echarts';
import B from './B.vue';
export default {
name: 'A',
components: {
B
},
provide: {
fruit: ['苹果', '葡萄', '香蕉'],
count: 3
}
}
</script>
(2)子组件 B.vue 比较简单,主要负责导入、注册和使用孙子组件。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是子组件B
<C></C>
</div>
</template>
<script>
import C from './C.vue';
export default {
name: 'B',
components: {
C
}
}
</script>
(3)孙组件 C 使用 inject 属性接收父组件传递的 fruit、count 属性,然后将接收到的数据绑定到 template 中进行显示。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是孙组件C<br>
fruit:{{ fruit }}<br>
count:{{ count }}
</div>
</template>
<script>
export default {
name: 'C',
inject: ['fruit', 'count']
}
</script>
(4)运行效果如下图所示,可以看到数据传输成功。

3,让 provide 响应式的正确写法
(1)provide 提供的都是静态数据,如果想在 provide 中提供 data 中定义的响应式数据, 那么我们可以将 provide 属性变为一个函数,函数返回一个对象。这样我们就可以在 provide 中通过 this 获取数据。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是父组件A
<B></B>
</div>
</template>
<script>
import { number } from 'echarts';
import B from './B.vue';
export default {
name: 'A',
components: {
B
},
provide() {
return {
fruit: this.fruit,
count: this.count
}
},
data() {
return {
fruit: ['苹果', '葡萄', '香蕉'],
count: 3
}
},
mounted() {
this.fruit.push('大梨子');
},
}
</script>
(2)运行结果如下,可以看到当 A 组件修改了 fruit 后,孙子组件注入的 fruit 也会跟着变化。由此可知,provide 提供的 fruit 属性是响应式数据。 也就是说后代拿到的是该对象的引用。
注意:如果注入的是一个对象(引用),子组件修改对象属性会影响祖先。
附:让原始值(number/string)也能是响应式
1,问题描述
(1)如果我们 provide() 一个普通值得副本(number/string 类型),后代拿到的是当时的值副本(非响应)。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是父组件A
<B></B>
</div>
</template>
<script>
import { number } from 'echarts';
import B from './B.vue';
export default {
name: 'A',
components: {
B
},
provide() {
return {
fruit: this.fruit,
count: this.count
}
},
data() {
return {
fruit: ['苹果', '葡萄', '香蕉'],
count: 3
}
},
mounted() {
this.fruit.push('大梨子');
this.count = 4;
},
}
</script>
(2)运行结果如下,可以看到虽然父组件 A 将 count 值变为了 4,但孙组件拿到的值仍然是 3。

2,解决办法
(1)如果想让 number/string 类型数据变成响应式的,可以使用 Vue.is 提供的 computed API。修改 A.vue 组件,让 count 属性接收一个计算属性。
<template>
<div style="border: solid 1px gray; padding: 20px;">
我是父组件A
<B></B>
</div>
</template>
<script>
import { number } from 'echarts';
import B from './B.vue';
import { computed } from 'vue';
export default {
name: 'A',
components: {
B
},
provide() {
return {
fruit: this.fruit,
count: computed(() => this.count)
}
},
data() {
return {
fruit: ['苹果', '葡萄', '香蕉'],
count: 3
}
},
mounted() {
this.fruit.push('大梨子');
this.count = 4;
},
}
</script>
(2)运行结果如下,可以看到当 A.vue 组件修改了“count”后,孙子组件注入的 count 属性也会相应地改变。

