本文主要介绍Vue3组件的七种通讯方法,并采用<script sutup>组合式API写法。

涉及到的知识点如下:

  • props
  • emit
  • expose/ref
  • v-model
  • provide/inject
  • mitt
  • Vuex/pinia

一、props【父组件传值给子组件】

<!-- Parent.vue -->
<template>
  <Child :meg="message"></Child>
</template>
<script setup>
import Child from "@/components/Child.vue";
let message = "Vue3父传子";
</script>
<!-- Child.vue -->
<template>
  {{ meg }}
</template>
<script setup>
const props = defineProps({
  meg: {
    type: String,
    default: "",
  },
});
console.log(props.meg); //在js中使用props.xxx 在html中可直接用xxx
</script>

 二、emit【子组件通知父组件触发一个事件,且可以传值给父组件】

<!-- Parent.vue -->
<template>
  <div>父组件:{{ message }}</div>
  <!-- 自定义 changeMsg 事件 -->
  <Child @changeMsg="changeMessage" />
</template>

<script setup>
import { ref } from "vue";
import Child from "@/components/Child.vue";
let message = ref("Vue3父传子");
// 更改 message 的值,data是从子组件传过来的
function changeMessage(data) {
  message.value = data;
}
</script>
<!-- Child.vue -->
<template>
  <div>子组件:<button @click="handleClick">子组件的按钮</button></div>
</template>
<script setup>
// 注册一个自定义事件名,向上传递时告诉父组件要触发的事件。
const emit = defineEmits(["changeMsg"]);
function handleClick() {
  // 参数1:事件名
  // 参数2:传给父组件的值
  emit("changeMsg", "子组件修改父组件的值");
}
</script>

三、expose / ref【子组件可以通过 expose 暴露自身的方法和数据,父组件通过 ref 获取到子组件并调用其方法或访问数据。

<!-- Parent.vue -->
<template>
  <div>父组件:拿到子组件的message数据:{{ msg }}</div>
  <button @click="childFun">调用子组件的方法</button>
  <hr />
  <Child ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from "vue";
import Child from "@/components/Child.vue";
const childRef = ref(null); // 通过 模板ref 绑定子组件
const msg = ref("");
onMounted(() => {
  // 在加载完成后,将子组件的 message 赋值给 msg
  msg.value = childRef.value.message;
});
function childFun() {
  // 调用子组件的 changeMessage 方法
  childRef.value.changeMessage("父组件调用子组件");
  // 重新将 子组件的message 赋值给 msg
  msg.value = childRef.value.message;
}
</script>
<!-- Child.vue -->
<template>
  <div>子组件:{{ message }}</div>
</template>
<script setup>
import { ref } from "vue";
const message = ref("子组件的值");
function changeMessage(data) {
  message.value = data;
}
//使用 defineExpose 向外暴露指定的数据和方法
defineExpose({
  message,
  changeMessage,
});
</script>

四、v-model【支持多个数据双向绑定

<!-- Parent.vue -->
<template>
  <Child v-model:msg1="message1" v-model:msg2="message2" />
</template>
<script setup>
import { ref } from "vue";
import Child from "@/components/Child.vue";
const message1 = ref("标题1");
const message2 = ref("标题2");
</script>
<!-- Child.vue -->
<template>
  <div><button @click="changeMsg1">修改msg1</button> {{ msg1 }}</div>
  <div><button @click="changeMsg2">修改msg2</button> {{ msg2 }}</div>
</template>
<script setup>
import { ref } from "vue";
// 接收
const props = defineProps({
  msg1: String,
  msg2: String,
});
const emit = defineEmits(["update:msg1", "update:msg2"]);
function changeMsg1() {
  emit("update:msg1", "修改标题1");
}
function changeMsg2() {
  emit("update:msg2", "修改标题2");
}
</script>

五、provide / inject【多层传值】

        **注:遇到多层传值时,使用 props 和 emit 的方式会显得比较笨拙。这时就可以是用provide和inject 。

        provide 是在父组件里使用的,可以往下传值。inject 是在子(后代)组件里使用的,可以往上取值。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。

<!-- Parent.vue -->
<template>
  <Child></Child>
</template>
<script setup>
import { ref, provide, readonly } from "vue";
import Child from "@/components/Child.vue";
const msg1 = ref("");
const msg2 = ref("标题1");
// 使用readonly后子组件直接修改会发出警告,需要调用provide往下传的方法来修改
provide("msg1", readonly(msg1));
provide("msg2", msg2);
provide("changeName", (value) => {
  msg1.value = value;
});
</script>
<!-- Child.vue -->
<template>
  <div>
    <div>msg1: {{ msg1 }}</div>
    <div>msg2: {{ msg2 }}</div>
    <button @click="handleClick">修改</button>
  </div>
</template>
<script setup>
import { inject } from "vue";
const msg1 = inject("msg1", "默认值"); // 看看有没有值,没值的话就适用默认值。
const msg2 = inject("msg2");
const changeName = inject("changeName");
function handleClick() {
  // 这样写不合适,因为vue里推荐使用单向数据流,当父级使用readonly后,子元素直接修改会发出警告。
  // msg1.value = '修改后的值'
  // 正确的方式
  changeName("修改后的标题1");
  // 因为 msg2 没被 readonly 过,所以可以直接修改值
  msg2.value = "标题2";
}
</script>

六、mitt

        Vue3 中没有了 EventBus 跨组件通信,但是现在有了一个替代的方案 mitt.js,原理还是 EventBus。

//mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt
// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// 组件 B
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>
12-12 14:31