Vue 3 提供了多种组件通信的方法,这里罗列了九种种方法;
Props(属性)
- 父组件可以通过属性传递数据给子组件。
- 子组件通过
props
接收父组件传递的数据。
<!-- 父组件 -->
<template>
<ChildComponent :message="parentMessage" />
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from "vue";
let parentMessage = ref('Hello from parent!');
</script>
<!-- 子组件 -->
<template>
<!-- 可以使用 props.message 或者省略peops直接用message -->
<div>{{ props.message }}</div>
<div>{{ message }}</div>
</template>
<script setup>
//确实不需要显式引入 defineProps
//因为 Vue 3 在 script setup 中会自动引入和注入 defineProps
let props = defineProps(['message']);//参数可以是对象或者数组,且数据是只读的,!!!不可以直接改
</script>
自定义事件(Custom Events)
- 子组件可以通过
$emit
触发自定义事件。 - 父组件通过
v-on
监听这些事件。
<!-- 子组件 -->
<template>
<button @click="sendMessage">Send Message</button>
</template>
<script setup>
//利用defineEmits方法返回函数触发自定义事件
//defineEmits方法不需要引入直接使用
//如果参数变成['message-sent','click'],那么click原生事件变成自定义事件,可供子组件调用
let messageEmits = defineEmits(['message-sent']);
const sendMessage=()=>{
//第一个参数为事件类型 第二 ~ N参数为注入传参数据
messageEmits('message-sent','first message','second message')
}
</script>
<!-- 父组件 -->
<template>
<ChildComponent @message-sent="handleMessage" @click="handlerClick"/>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
//自定义事件回调
const handleMessage = (data1,data2)=>{
//事件传参
console.log(data1,data2);//打印结果为 'first message','second message'
}
//这里是原生的click事件
const handlerClick = (event)=>{
//event即为事件对象
console.log(event);
}
</script>
mitt 全局事件总线:
mitt 是一个小巧且强大的事件总线库,可以用于更灵活的组件通信。使用 mitt,你可以在任何组件之间进行事件的发布和订阅。
- 安装 mitt:
npm install mitt
- 在组件中使用 mitt:
独立声明,方便按需引入
<!-- /src/emitter/index.js -->
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
<!-- 父组件 -->
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script setup>
import emitter from "@/emitter";
const sendMessage = ()=>{
//发布消息触发事件使用emit方法,第一个参数是自定义事件类型,后面是传输的消息
emitter.emit('message-sent', 'Hello from parent!');
}
</script>
<!-- 子组件 -->
<template>
<div>
<h3>{{ message }}</h3>
</div>
</template>
<script setup>
import emitter from "@/emitter";
//组合式API函数
import { onMounted,ref } from "vue";
//第一个参数:即为自定义事件类型 第二个参数:即为事件回调
let message= ref('')
onMounted(() => {
emitter.on("message-sent", (mes) => {
console.log(mes);
message.value = mes
});
});
</script>
使用 mitt 可以更轻松地进行多组件之间的通信,而不需要通过父子组件关系或者 provide/inject 来传递数据。mitt 的使用方式类似于浏览器的事件系统,更具灵活性。
组件v-model 通信
v-model 是 Vue 3 中用于实现父子组件之间双向绑定的一种机制。通过使用 v-model,可以在父组件中使用 v-model 指令将数据双向绑定到子组件的 prop 和事件上,使得父子组件之间的通信更加方便。
Vue 3 默认期望子组件接收一个名为 modelValue 的 prop,并且通过 update:modelValue 事件来实现双向绑定。不过,你确实可以自定义这个名字。
这里就直接演示自定义的prop,可能这块有一些不还好理解,因为 **v-model 组件通信 **里层帮我们做了很多事,让我们省去了很多重复性的代码,它对于封装组件非常有效且干净,以下我用案例来解析一下;
<!-- ParentComponent.vue -->
<template>
<div>
<!-- customProp为我自定义的名字
这里的v-model做了以下事
第一:给子组件传递 props[customProp] = 'Hello from parent!'
第二:给子组件绑定自定义事件update:customProp
-->
<ChildComponent v-model:customProp="parentMessage" />
<p>Message in parent: {{ parentMessage }}</p>
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
const parentMessage = ref('Hello from parent!');
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<input :value="customProp" @input="updateCustomProp" />
<p>Message in child: {{ customProp }}</p>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
const props = defineProps(['customProp']);
const { emit } = defineEmits(['update:customProp']);
const updateCustomProp = (event) => {
emit('update:customProp', event.target.value);
};
</script>
我将 v-model 的 prop 名称设为了 customProp。父组件通过 v-model:customProp=“parentMessage” 将 parentMessage 数据绑定到子组件上,子组件内部使用 :value 和 @input 来绑定输入框的值和触发更新事件。这样,当子组件的输入框变化时,会通过 update:customProp 事件通知父组件更新数据。
Provide / Inject(提供/注入)
父组件通过 provide
提供数据,子孙组件通过 inject
注入数据,可以实现隔辈组件传递数据,这里用三个组件案例来说明;
<!-- 父组件 -->
<template>
<div>
<p>Message in parent: {{ message }}</p>
<ChildComponent />
</div>
</template>
<script setup>
import { provide, ref } from 'vue';
const message = ref('Hello from parent!');
// 使用 provide 提供数据
provide('message', message);
</script>
<!-- 子组件 -->
<template>
<div>
<p>Message in child: {{ childMessage }}</p>
<GrandchildComponent />
</div>
</template>
<script setup>
import { inject, ref } from 'vue';
import GrandchildComponent from './GrandchildComponent.vue';
// 使用 inject 接收提供的数据
const childMessage = inject('message');
</script>
<!-- 孙组件 -->
<template>
<div>
<p>Message in grandchild: {{ grandchildMessage }}</p>
<button @click="updateMessage">孙子修改数据</button>
</div>
</template>
<script setup>
import { inject } from 'vue';
// 使用 inject 接收提供的数据
const grandchildMessage = inject('message');
//可以直接更新父组件
const updateMessage = ()=>{
grandchildMessage.value = 'Default message from grandchild';
}
</script>
在这个例子中,ParentComponent 组件通过 provide 提供了一个名为 ‘message’ 的数据,然后 ChildComponent 组件和 GrandchildComponent 组件分别通过 inject 接收了这个数据。这样就实现了隔代组件通信,父组件的数据可以被子组件和孙子组件访问到,同时子孙也可以直接修改数据同步到父组件;
请注意,使用 provide 和 inject 时,要确保在子组件中使用 inject 接收提供的数据时,给定的 key(这里是 ‘message’)要与父组件中 provide 的 key 一致。
useAttrs
useAttrs
是 Vue 3 中的一个 Composition API 函数,它可以用于在组件中访问父组件传递的所有属性。通过 useAttrs
,你可以获取父组件传递给当前组件的所有属性,并在需要的时候使用它们。
以下是一个简单的例子:
下是一个简单的示例,演示如何使用 useAttrs:
<template>
<div>
<p>Message: {{ message }}</p>
<p>Additional Attribute: {{ additionalAttribute }}</p>
</div>
</template>
<script setup>
import { useAttrs, ref } from 'vue';
const { message, additionalAttribute } = useAttrs();
</script>
在这个例子中,useAttrs 会返回一个包含组件上所有未声明的属性的对象。你可以通过解构赋值获取其中的属性值。这样,即使在组件上没有声明这些属性,你仍然可以访问它们。
例如,如果你使用这个组件时传递了一个未在组件中声明的属性:
<MyComponent message="Hello from parent" additionalAttribute="Additional Data" />
那么在组件内部,通过 useAttrs 就可以获取这两个属性的值。需要注意的是,如果属性在组件中声明了,它将不会出现在 useAttrs 的返回值中,比如这里同时使用使用了defineProps来接收参数message,那么message就不在useAttrs 中返回了
Ref(引用)和$prarent
- 父组件可以通过
ref
获取子组件的引用。
<!-- 父组件 -->
<template>
<child-component ref="childRef" />
<h3>{{parsentMessage}}</h3>
<button @click="getChildData">拿到儿子暴露的的属性和方法</button>
</template>
<script setup>
import {ref} from 'vue';
import ChildComponent from './ChildComponent.vue';
//获取子组件的实例
let childRef = ref();
const parsentMessage = ref('parent show message')
const getChildData = ()=>{//使用ref修改数据或者调用方法,前提是子组件存在该方法且暴露出来
childRef.value.childMessage = 'updata child show message'
}
//对外暴露,只有对外暴露了ref或者$parent才能查询到
defineExpose({
parsentMessage
})
</script>
<!-- 子组件 -->
<template>
<!-- Child component template -->
<div>
<h3>{{childMessage}}</h3>
<button @click="getparentData">拿到父亲暴露的属性和方法</button>
</div>
</template>
<script setup>
import {ref} from 'vue';
const childMessage = ref('child show message')
const getparentData = ()=>{
}
//对外暴露,只有对外暴露了ref或者$parent才能查询到
defineExpose({
childMessage
})
</script>
Pinia(状态管理库)
Pinia
是一个状态管理库,它可以在 Vue 3 项目中用于全局状态管理,而且也可以用于组件通信。虽然 Pinia
主要是为状态管理而设计的,但它的 store 实例也可以用于简单的组件通信。
以下是一个简单的例子,演示了如何使用 Pinia
在组件之间进行通信:
- 安装 Pinia:
npm install pinia
- 创建 Pinia Store:
// src/pinia/index.js
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;
- 创建 Pinia Store 实例:
// src/pinia/store.js
import { defineStore } from 'pinia';
export const usePinia = defineStore('pinia', {
state: () => ({
message: ''
}),
actions: {
setMessage(message) {
this.message = message;
}
}
});
- 在组件中使用 Pinia:
<!-- ParentComponent.vue -->
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script setup>
import {usePinia} from "@/pinia/store";
//获取Pinia对象
let infoStore = usePinia();
const sendMessage =()=> {
// 在 Pinia store 中设置消息
infoStore.setMessage('Hello from parent!');
}
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<p>Received message: {{ infoStore.message }}</p>
</div>
</template>
<script setup>
import pinia from '@/pinia';
import {usePinia} from "@/pinia/store";
//获取Pinia对象
let infoStore = usePinia();
</script>
- 在 main.js 中挂载 Pinia:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import pinia from '@/pinia';
import { createPinia } from 'pinia';
const app = createApp(App);
app.use(pinia);
app.mount('#app');
上述例子中,我们使用 Pinia
创建了一个全局的 store,并在父组件中通过 store 设置消息,然后在子组件中通过 store 获取消息,实现了组件之间的通信。请注意,这只是 Pinia
在组件通信方面的一种用法,Pinia
主要是用于全局状态管理,如果你的应用有更复杂的状态管理需求,Pinia
提供了更丰富的功能。
插槽(Slots)父子通信
通过插槽(Slots)也是一种在 Vue 组件之间进行通信的方法。插槽允许父组件向子组件传递内容,子组件可以在指定的位置插入这些内容。这样,父组件和子组件之间可以进行更灵活的通信。
以下是一个简单的例子:
<!-- ParentComponent.vue -->
<template>
<div>
<childComponent>
<!-- 通过插槽传递数据 -->
<template v-slot:messageSlot="{ message }">
<p>Received message: {{ message }}</p>
</template>
</childComponent>
</div>
</template>
<script setup>
import childComponent from "./child-component.vue";
</script>
<!-- ChildComponent.vue -->
<template>
<div>
<!-- 使用插槽接收数据 -->
<slot name="messageSlot" :message="message" />
</div>
</template>
<script setup>
import { defineComponent, ref } from 'vue';
import {ref} from 'vue'
const message= ref('Hello from child!')
</script>
在上面的例子中,通过使用插槽,父组件向子组件传递了一个名为 messageSlot
的插槽,并在插槽内部渲染了一个包含消息的 <p>
元素。子组件则通过 <slot>
元素接收并渲染这个插槽,并将 message
属性传递给插槽。
通过这种方式,父组件和子组件可以更加自由地通信,父组件可以决定传递什么内容,而子组件可以在不同的插槽中接收和处理这些内容。这种方式尤其适用于需要更灵活组件结构的情况。
这些是一些常见的 Vue 3 组件通信的方法。选择合适的方法取决于你的具体需求和项目结构;
项目中大部分组件通信使用 props 和自定义事件就是可解决;