当前位置: > > > Vue.js - 使用Provide/Inject实现跨层级组件通信教程(祖孙组件的数据传递)

Vue.js - 使用Provide/Inject实现跨层级组件通信教程(祖孙组件的数据传递)

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

1,基本介绍

(1)ProvideInject 用于非父子组件之间共享数据。例如,在深度嵌套的组件中,子组件需要取父组件的部分内容。在这种情况下,如果仍然沿着组件链逐级传递 props,则会非常麻烦针对这种非父子组件之间的情况,我们可以使用 ProvideInject 实现数据共享。

(2)可以看到,在父组件中使用 Provide 提供内容,在孙子组件中使用 Iniect 注入祖父提供的内容,好处如下: 
  • 无论层级结构多深,父组件都可以作为其所有子组件和孙子组件的依赖提供者。
  • 父组件不需要知道哪些子组件使用它提供(Provide)的属性(Property)。 
  • 子组件不需要知道注入(Inject)的属性来自哪里。 

2,基本用法

(1)假设我们有 A.vueB.vueC.vue 三个组件,分别代表父组件、子组件、孙子组件。其中父组件 A 主要负责导入、注册和使用 B.vue 子组件,同时使用 provide 属性向子组件和孙子组件共享 fruitcount 数据。这样子组件和孙子组件就可以使用 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 属性接收父组件传递的 fruitcount 属性,然后将接收到的数据绑定到 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)运行结果如下,可以看到虽然父组件 Acount 值变为了 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 属性也会相应地改变。
评论0