Vue3中的常见组件通信之provide
、inject
概述
在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。
组件关系 | 传递方式 |
---|---|
父传子 | 1. props 2. v-model 3. $refs 4. 默认插槽、具名插槽 |
子传父 | 1. props 2. 自定义事件 3. v-model 4. $parent 5. 作用域插槽 |
祖传孙、孙传祖 | 1. $attrs 2. provide、inject |
兄弟间、任意组件间 | 1. mitt 2. pinia |
props和自定义事件详见:
Vue3中的常见组件通信之props和自定义事件
mitt用法详见:
Vue3中的常见组件通信之mitt
v-model用法详见:
Vue3中的常见组件通信之v-model
$attrs
用法详见:
Vue3中的常见组件通信之$attrs
$refs
和$parent
详见:
Vue3中的常见组件通信之$refs
和$parent
接下来是provide和inject。
7.provide和inject
provide
和inject
用于当前组件向其后代组件直接通信,需要先在祖先组件中通过provide
配置向后代组件提供数据,然后在后代组件中通过inject
配置声明接收数据。
7.1准备三个组件
先准备三个组件,分别为父组件、子组件和孙组件,具体代码如下:
父组件代码:
vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>用户订单详情</h4>
<ul>
<li>订单编号:{{ oderDetail.id }}</li>
<li>订单用户:{{ oderDetail.username }}</li>
<li>订单商品:{{ oderDetail.goods }}</li>
<li>订单价格:{{ oderDetail.price }}</li>
</ul>
<Child/>
</div>
</template>
<script lang="ts" setup name="Father">
import Child from './Child.vue'
import {reactive} from 'vue'
let oderDetail = reactive({
id:"abc01",
username:'xiaopeng',
goods:"神仙水",
price:998
})
</script>
<style scoped>
.father{
background-color: rgb(112, 150, 66);
margin: 10px;
padding: 10px;
border-radius: 5px;
}
</style>
子组件代码:
vue
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild/>
</div>
</template>
<script lang="ts" setup name="Child">
import GrandChild from './GrandChild.vue'
</script>
<style scoped>
.child{
background-color: burlywood;
margin: 10px;
padding: 10px;
border-radius: 10px;
}
</style>
孙组件代码:
vue
<template>
<div class="grandChild">
<h3>
这是孙组件
</h3>
</div>
</template>
<script lang="ts" setup name="GrandChild">
</script>
<style scoped>
.grandChild{
background-color: aqua;
margin: 10px;
padding:10px;
border-radius: 5px;
}
</style>
运行效果如下:
7.2 祖传孙通信的实现
在父组件中引入provide,并提供数据
typescript
import {reactive,provide} from 'vue'
typescript
//提供数据
provide('oderDetail',oderDetail)
注意此处第一个参数的名字可以是任意的,第二个参数为数据,如果是多个数据,可以为对象格式的数据。
在后代组件中(以孙组件为例)引入inject,并注入数据:
typescript
import { inject } from 'vue';
//注入数据
let oderDetail = inject('oderDetail')
注意inject中的第一个参数必须与祖组件中provide中第一个参数相同,第二个参数为默认值,即当父组件中没有传递'oderDetail'
,那么孙组件中的oderDetail
的值就是procide中的第二个参数。
孙组件中在页面中呈现:
vue
<h4>父组件传递过来的用户订单详情</h4>
<ul>
<li>订单编号:{{ oderDetail.id }}</li>
<li>订单用户:{{ oderDetail.username }}</li>
<li>订单商品:{{ oderDetail.goods }}</li>
<li>订单价格:{{ oderDetail.price }}</li>
</ul>
注意上面代码中VS Code进行TS检查,会提示oderDetail有错误,但是实际不影响页面呈现,运行结果如下:
接下来需要处理ts的问题,ts显示oderDetail类型为未知,那么可以在定义oderDetail的时候给个默认值,如下代码:
typescript
let oderDetail = inject('oderDetail',{id:'',username:'',goods:'',price:0})
此时VS Code就不再报错,并且运行结果一样。
7.3 孙传祖通信的实现
provide和inject也可以实现孙传祖通信,需要在父组件中定义一个函数,并传递给后代:
typescript
//方法
function discount(value:number){
oderDetail.price = oderDetail.price * value/10
}
//提供方法
provide('discount',discount)
孙组件中接收方法:
typescript
let discount = inject('discount',(value:number)=>{})
孙组件中添加按钮并绑定单击事件触发接收的方法,并传递参数:
vue
<button @click="discount(7)">父组件中的订单价格打7折</button>
运行后单击按钮可以实现更改父组件中的价格,由于孙组件中接收的数据为相应式的,因此更改父组件中的价格,孙组件中的订单价格也会相应变化,如下图所示:
至此以及实现了孙传祖通信。
不过上面代码还可以简化,之前提到过provide
第二个参数如果是多个数据,可以为对象格式的数据,这样就可以把数据和对象同时传递和接收,如下代码所示:
typescript
//提供数据和方法
provide('oderContent',{oderDetail,discount})
typescript
let {oderDetail,discount} = inject('oderContent',{oderDetail:{id:'',username:'',goods:'',price:0},discount:(value:number)=>{}})
这样运行的结果是完全一样的。
7.4 小结
provide
和inject
用于当前组件向其后代组件直接通信,需要先在祖先组件中通过provide
配置向后代组件提供数据,然后在后代组件中通过inject
配置声明接收数据。这个过程是完全不打扰中间的子组件,实现的是祖孙间的直接通信。
下面是完整代码:
父组件:
vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>用户订单详情</h4>
<ul>
<li>订单编号:{{ oderDetail.id }}</li>
<li>订单用户:{{ oderDetail.username }}</li>
<li>订单商品:{{ oderDetail.goods }}</li>
<li>订单价格:{{ oderDetail.price }}</li>
</ul>
<Child/>
</div>
</template>
<script lang="ts" setup name="Father">
import Child from './Child.vue'
import {reactive,provide} from 'vue'
//数据
let oderDetail = reactive({
id:"abc01",
username:'xiaopeng',
goods:"神仙水",
price:998
})
//方法
function discount(value:number){
oderDetail.price = oderDetail.price * value/10
}
// //提供数据
// provide('oderDetail',oderDetail)
// 提供方法
// provide('discount',discount)
//提供数据和方法
provide('oderContent',{oderDetail,discount})
</script>
<style scoped>
.father{
background-color: rgb(112, 150, 66);
margin: 10px;
padding: 10px;
border-radius: 5px;
}
</style>
子组件:
vue
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild/>
</div>
</template>
<script lang="ts" setup name="Child">
import GrandChild from './GrandChild.vue'
</script>
<style scoped>
.child{
background-color: burlywood;
margin: 10px;
padding: 10px;
border-radius: 10px;
}
</style>
孙组件:
vue
<template>
<div class="grandChild">
<h3>
这是孙组件
</h3>
<h4>父组件传递过来的用户订单详情</h4>
<ul>
<li>订单编号:{{ oderDetail.id }}</li>
<li>订单用户:{{ oderDetail.username }}</li>
<li>订单商品:{{ oderDetail.goods }}</li>
<li>订单价格:{{ oderDetail.price }}</li>
</ul>
<button @click="discount(7)">父组件中的订单价格打7折</button>
</div>
</template>
<script lang="ts" setup name="GrandChild">
import { inject } from 'vue';
// // 注入数据
// let oderDetail = inject('oderDetail',{id:'',username:'',goods:'',price:0})
// let discount = inject('discount',(value:number)=>{})
let {oderDetail,discount} = inject('oderContent',{oderDetail:{id:'',username:'',goods:'',price:0},discount:(value:number)=>{}})
</script>
<style scoped>
.grandChild{
background-color: aqua;
margin: 10px;
padding:10px;
border-radius: 5px;
}
</style>