总结下vue3组件通信方法
props
父子传参emit
自定义事件mitt
事件总线v-model
$attrs
$refs、$parent
provide、inject
pinia
slot
1. props父子传参
props变量可以实现父子组件传参
父组件通过props不仅可以传递变量,还可以传递函数给子组件
具体可查看vue官网props
1.父传子
父组件定义car
变量,通过:car="car"
传递给子组件
子组件通过defineProps
接收car
变量
2.子传父
父组件定义getToy
方法,通过:getToy="getToy"
传递给子组件,
子组件调用getToy
方法,并将toy
子组件变量作为getToy
的参数传递给父组件
父组件
<template>
<div class="father">
<h3>这是父组件</h3>
<h4>父组件的变量car:{{ car }}</h4>
<h4>子组件通过getToy传递变量toy给父组件:{{ toy }}</h4>
<!-- 父组件将car变量和getToy函数都传递给子组件 -->
<Child :car="car" :getToy="getToy" />
</div>
</template>
<script setup name="Father">
import { ref } from "vue";
import Child from "./Child.vue";
// 父组件定义car变量,通过props传递给子组件,即:car:"car"
const car = ref("小米");
const toy = ref("");
// 如何实现子组件传参
// 父组件定义getToy方法,通过props传递给子组件,即:getToy:"getToy"
function getToy(value) {
// 定义的toy变量接收子组件传递过来的value(子组件变量toy),即实现子组件传参
toy.value = value;
}
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 400px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件
<template>
<div class="child">
<h3>这是子组件</h3>
<h4>子组件的变量toy:{{ toy }}</h4>
<h4>子组件接收父组件的变量car:{{ car }}</h4>
<button @click="handleClick(toy)">点击将子组件变量toy传递给父组件</button>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
const toy = ref("奥特曼");
// 子组件通过defineProps接收父组件传递的car变量和getToy方法
const props = defineProps(["car", "getToy"]);
// 点击事件
function handleClick(value) {
// 子组件调用父组件传递过来的getToy方法,并把变量toy作为getToy的参数传入
props.getToy(value);
}
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 200px;
background-color: #f1dbbb;
padding: 20px;
}
</style>
2. 自定义事件
自定义事件用于实现子组件传参
- 子组件通过deineProps先声明自定义事件
const emit = defineEmits(["sendToy"]);
想要传参时触发emit("sendToy", "666")
- 父组件监听自定义事件,
@send-toy="sendToy"
具体可查看vue官网组件事件
父组件
<template>
<div class="father">
<h3>这是父组件</h3>
<h4>子组件通过自定义事件传递变量toy给父组件:{{ toy }}</h4>
<!-- 给子组件child绑定自定义事件 -->
<Child @sendToy="sendToy" />
</div>
</template>
<script setup name="Father">
import { ref } from "vue";
import Child from "./Child.vue";
const toy = ref("");
// 如何实现子组件传参
function sendToy(value) {
// 子组件传递过来的value(即变量toy),实现子组件传参
toy.value = value;
}
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 400px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件
<template>
<div class="child">
<h3>这是子组件</h3>
<h4>子组件变量toy:{{ toy }}</h4>
<!-- 方法一: 声明触发的事件-->
<button @click="handleClick(toy)" class="btn">
点击将子组件变量toy传递给父组件
</button>
<!-- 方法二:在 <template> 中直接使用$emit 方法 -->
<button @click="$emit('sendToy', toy)">
通过$emit将子组件变量toy传递给父组件
</button>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
// 声明触发的自定义事件,可通过 defineEmits() 宏来声明它要触发的事件
const emit = defineEmits(["sendToy"]);
const toy = ref("奥特曼");
// 点击事件
function handleClick(value) {
// 触发sendToy事件
emit("sendToy", value);
}
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 200px;
background-color: #f1dbbb;
padding: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
3. mitt事件总线
概述:与消息订阅与发布(pubsub
)功能类似,可以实现任意组件间通信。
- 提供数据的组件,在合适的时候触发事件
- 接收数据的组件中:绑定事件、同时在销毁前解绑事件
Vue2.x 使用 EventBus 事件总线进行兄弟组件通信,而在Vue3中事件总线模式已经被移除,官方建议使用外部的、实现了事件触发器接口的库,例如 mitt
- 安装
mitt
npm i mitt
- 新建文件:
src\utils\emitter.ts
// 引入mitt
import mitt from "mitt";
// 创建emitter
const emitter = mitt()
// 创建并暴露mitt
export default emitter
- 如何使用
父组件
<template>
<div class="father">
<h3>这是父组件</h3>
<Child1 />
<Child2 />
</div>
</template>
<script setup name="Father">
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 420px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件1
<template>
<div class="child">
<h3>这是子组件1</h3>
<h4>子组件1的变量toy:{{ toy }}</h4>
<button @click="handleClick(toy)">
通过emitter将子组件1的变量toy传递给子组件2
</button>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
// 引入mitt
import emitter from "@/utils/emitter";
const toy = ref("奥特曼");
// 点击事件
function handleClick(value) {
// 触发sendToy事件
emitter.emit("sendToy", value);
}
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 150px;
background-color: #f1dbbb;
padding: 20px;
margin-bottom: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
子组件2
<template>
<div class="child">
<h3>这是子组件2</h3>
<h4>子组件2接收子组件1传递的变量toy:{{ toy }}</h4>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
import { onMounted, onUnmounted } from "vue";
import emitter from "@/utils/emitter";
const toy = ref("");
onMounted(() => {
// 监听sendToy事件
emitter.on("sendToy", (value) => {
toy.value = value;
});
});
onUnmounted(() => {
// 解绑事件
emitter.off("sendToy");
// 移除全部事件
// emitter.all.clear()
});
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 100px;
background-color: #f1dbbb;
padding: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
4. v-model
概述:实现 父组件
和 子组件
之间相互通信。
父组件
<template>
<div class="father">
<h3>这是父组件</h3>
<h3>v-model绑定的变量userName: {{ userName }}</h3>
<!-- 组件标签上使用v-model命令 -->
<Child1 v-model="userName" />
<!-- 本质为 -->
<!-- <Child1 :modelValue="userName" @update:model-value="userName = $event" /> -->
</div>
</template>
<script setup name="Father">
import { ref } from "vue";
import Child1 from "./Child1.vue";
let userName = ref();
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 250px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件
<template>
<div class="child">
<h3>这是子组件</h3>
<!--将接收的value值赋给input元素的value属性,目的是:为了呈现数据 -->
<input type="text" :value="modelValue" @input="handleInput" />
</div>
</template>
<script setup name="Child">
// 父组件通过v-model="xxx"的形式传递给子组件变量,子组件接收的变量默认为modelValue
const props = defineProps(["modelValue"]);
// 更新modelValue的方法默认为update:modelValue
const emit = defineEmits(["update:modelValue"]);
// 给input元素绑定原生input事件,调用handleInput方法时,进而触发update:model-value事件
const handleInput = ($event) => {
emit("update:modelValue", $event.target.value);
};
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 100px;
background-color: #f1dbbb;
padding: 20px;
margin-bottom: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
注意:如何更换modelValue
// 父组件
<!-- 通过v-model:xxx="yyy"的形式传递,即可将默认的modelValue修改为xxx -->
<Child v-model:mingzi="userName"/>
<!-- 上面代码的本质如下 -->
<Child :mingzi="userName" @update:mingzi="userName = $event"/>
// 子组件
<template>
<div class="child">
<input type="text" :value="mingzi" @input="handleInput" />
</div>
</template>
<script setup name="Child">
// 父组件通过v-model:xxx="yyy"的形式传递给子组件变量,子组件接收的变量默认为xxx
const props = defineProps(["mingzi"]);
// 更新modelValue的方法默认为update:xxx
const emit = defineEmits(["update:mingzi"]);
// 给input元素绑定原生input事件,调用handleInput方法时,进而触发update:model-value事件
const handleInput = ($event) => {
emit("update:mingzi", $event.target.value);
};
</script>
5.$attrs
-
概述:
$attrs
用于实现当前组件的父组件,向当前组件的子组件通信(祖→孙)。 -
说明:
$attrs
是一个对象,包含所有父组件传入的标签属性。注意:
$attrs
会自动排除props
中声明的属性(可以认为声明过的props
被子组件自己“消费”了)
父组件
<template>
<div class="father">
<h3>父组件</h3>
<!-- v-bind="{ x: 400, y: 500 }" 相当于:x="x" :y="y",和v-bind="$attr"同理 -->
<Child
:a="a"
:b="b"
:c="c"
v-bind="{ x: 400, y: 500 }"
:updateA="updateA"
/>
</div>
</template>
<script setup name="Father">
import Child from "./Child.vue";
import { ref } from "vue";
let a = ref(100);
let b = ref(200);
let c = ref(300);
// 方法也可传递
function updateA(value) {
b.value = 0;
}
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 500px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件
<template>
<div class="child">
<h3>子组件</h3>
<h4>获取父组件传递的a: {{ a }}</h4>
<!-- $attrs传递的变量包含父组件传递的所有属性,除去子组件中已经声明的属性a -->
<GrandChild v-bind="$attrs" />
</div>
</template>
<script setup name="Child">
import GrandChild from "./GrandChild.vue";
// 子组件中已声明属性a,其余父组件传递的属性都在$attr中
const props = defineProps(["a"]);
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 450px;
background-color: #f1dbbb;
padding: 20px;
margin-bottom: 20px;
box-sizing: border-box;
.btn {
margin-bottom: 20px;
}
}
</style>
孙组件
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(666)">点击更新b变量为0</button>
</div>
</template>
<script setup name="GrandChild">
defineProps(["b", "c", "x", "y", "updateA"]);
</script>
<style lang="scss" scoped>
.grand-child {
width: 300px;
height: 300px;
background-color: #c3f4fa;
padding: 20px;
margin-bottom: 20px;
box-sizing: border-box;
.btn {
margin-bottom: 20px;
}
}
</style>
6.$parent
、$refs
属性 | 说明 |
---|---|
ref | 对dom元素或子组件添加ref属性,并声明一个与ref属性名称相同的变量,就能拿到该dom元素或者子组件实例 |
$refs | 值为对象,包含所有被ref 属性标识的DOM 元素或组件实例。用于父传子 |
$parent | 值为对象,当前组件的父组件实例对象。用于子传父 |
父组件
<template>
<div class="father">
<!-- 对dom元素h3添加ref属性c3 -->
<h3 ref="c3">这是父组件</h3>
<h4>父组件的变量toy: {{ toy }}</h4>
<button @click="animalChange()" class="btn1">
将子组件1的变量修改为"边牧安娜"
</button>
<!-- $refs为所有被`ref`属性标识的`DOM`元素或组件实例 -->
<button @click="allChildGet($refs)">
将两个子组件的变量都修改为“柯基”
</button>
<!-- 对子组件1添加ref属性c1 -->
<Child1 ref="c1" />
<!-- 对子组件1添加ref属性c2 -->
<Child2 ref="c2" />
</div>
</template>
<script setup name="Father">
import { ref } from "vue";
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue";
// 对子组件1添加ref属性,声明一个与ref属性名称相同的变量c1,c1即为实例子组件1
let c1 = ref();
// 对子组件2添加ref属性,声明一个与ref属性名称相同的变量c2,c2即为实例子组件2
let c2 = ref();
// 对dom元素h3添加ref属性,声明一个与ref属性名称相同的变量c3,c3即为DOM元素h3
let c3 = ref();
let toy = ref("迪迦");
// 将子组件1的变量dog修改为"边牧安娜"
const animalChange = () => {
c1.value.dog = "边牧安娜";
};
const allChildGet = (refs) => {
// refs为所有被`ref`属性标识的`DOM`元素或组件实例,包含c1,c2,c3
console.log(/refs/, refs);
refs["c1"].dog = "柯基";
refs["c2"].dog = "柯基";
};
// 向外部提供数据
defineExpose({
toy
});
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 500px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
.btn1 {
margin-right: 10px;
}
}
</style>
子组件1
<template>
<div class="child">
<h3>这是子组件1</h3>
<h4>子组件1的变量dog:{{ dog }}</h4>
<button @click="changeBack($parent)">修改父组件的变量toy</button>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
const dog = ref("边牧贝拉");
// 修改父组件变量toy
const changeBack = (parent) => {
parent.toy = "加班";
};
// dog变量为子组件私有,需要通过defineExpose宏函数将变量交出去,外部才能读取该变量
defineExpose({
dog
});
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 150px;
background-color: #f1dbbb;
padding: 20px;
margin-bottom: 20px;
margin-top: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
子组件2
<template>
<div class="child">
<h3>这是子组件2</h3>
<h4>子组件2的变量dog:{{ dog }}</h4>
</div>
</template>
<script setup name="Child">
import { ref } from "vue";
const dog = ref("萨摩耶大白");
// dog变量为子组件私有,需要通过defineExpose宏函数将变量交出去,外部才能读取该变量
defineExpose({
dog
});
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 100px;
background-color: #f1dbbb;
padding: 20px;
.btn {
margin-bottom: 20px;
}
}
</style>
7. provide
、inject
- 概述:允许一个祖先组件向其所有子孙后代注入一个依赖,实现祖孙组件直接通信,查看官网provide、inject的使用
- 具体使用
属性 | 说明 |
---|---|
provide | 在祖先组件中通过provide 配置向后代组件提供数据 |
inject | 在后代组件中通过inject 配置来声明接收数据 |
父组件
<template>
<div class="father">
<h3>父组件</h3>
<Child />
</div>
</template>
<script setup name="Father">
import Child from "./Child.vue";
import { ref, reactive, provide } from "vue";
// 数据
let money = ref(100);
let dog = ref("边牧贝拉");
// 更新money
function updateMoney(value) {
money.value += value;
}
// 要为组件后代提供数据,需要使用到 provide() 函数
// 第一个参数被称为注入名,可以是一个字符串或是一个 Symbol
// 第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref
provide("dog", dog);
// 和响应式数据配合使用,在供给方组件内声明并提供一个更改数据的方法函数
provide("moneyContext", { money, updateMoney });
</script>
<style lang="scss" scoped>
.father {
width: 500px;
height: 500px;
background-color: #f0ebeb;
padding: 20px;
text-align: left;
}
</style>
子组件
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild />
</div>
</template>
<script setup name="Child">
import GrandChild from "./GrandChild.vue";
</script>
<style lang="scss" scoped>
.child {
width: 400px;
height: 450px;
background-color: #f1dbbb;
padding: 20px;
margin-bottom: 20px;
box-sizing: border-box;
.btn {
margin-bottom: 20px;
}
}
</style>
孙组件
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>零花钱:{{ money }}</h4>
<h4>宠物:{{ dog }}</h4>
<button @click="updateMoney(1)">点击零花钱+1</button>
</div>
</template>
<script setup name="GrandChild">
// 要注入上层组件提供的数据,需使用 inject() 函数
import { inject } from "vue";
// 第一个参数为注入名,第二个参数为默认值,如果没有祖先组件提供 "dog",`value` 会是 "默认值"
let dog = inject("dog", "默认值");
// 注入数据
let { money, updateMoney } = inject("moneyContext", {
money: 0,
updateMoney: () => {}
});
</script>
<style lang="scss" scoped>
.grand-child {
width: 300px;
height: 300px;
background-color: #c3f4fa;
padding: 20px;
margin-bottom: 20px;
box-sizing: border-box;
.btn {
margin-bottom: 20px;
}
}
</style>