当前位置: 首页 > news >正文

Vue 组件有哪些通讯方式?这里有10种方式及示例代码

你好同学,我是沐爸,欢迎点赞、收藏、评论和关注。

Vue 的组件通讯方式到底有几种?还真说不好。不过最常用、最常见的是以下十种:

props、 e m i t 、 emit、 emitparent/ c h i l d r e n 、 children、 childrenrefs、provide/inject、 a t t r s / attrs/ attrs/listeners、vuex/pinia、 eventBus/mitt、slot 作用域插槽、本地缓存

接下来一起看下各种通讯方式的特点、使用场景以及注意事项吧!Let’s go!

一、Props

  • 概述:通过 props 属性,父组件可以向子组件传递数据。
  • 特点:单向数据流,子组件不能直接修改 props 中的值(但可以通过触发事件请求父组件修改)。

父组件

<template><child-component :count="count"></child-component>
</template><script>import { ref } from "vue";import ChildComponent from "./ChildComponent.vue";const count = ref(100);
</script>

子组件

<template><div>{{ count }}</div>
</template><script setup>const props = defineProps(["count"]);console.log(props.count);
</script>

二、$emit

  • 概述:子组件可以通过 $emit 触发事件,并传递数据给父组件。
  • 特点:父组件需要监听这些事件来接收数据。

父组件

<template><child-component:count="count"@increment="count += $event"></child-component>
</template><script setup>import { ref } from "vue";import ChildComponent from "./ChildComponent.vue";const count = ref(100);
</script>

子组件

<template><div><div>{{ count }}</div><button @click="$emit('increment', 2)">点一下</button></div>
</template><script setup>const props = defineProps(["count"]);console.log(props.count);
</script>

注意,如果在子组件的 template 模板上使用 $emit,请确保子组件只有一个根节点,否则会报警告。如果不想增加额外根节点,解决警告的另一种方式是使用 defineEmits

<template><div>{{ count }}</div><button @click="handleClick">Increment</button>
</template><script setup>const props = defineProps(["count"]);console.log(props.count);const emit = defineEmits(["increment"]);function handleClick() {emit("increment", 2);}
</script>

三、$parent / $children

  • 概述:$parent 用于访问组件的父实例;$children 用于访问当前实例的直接子组件(仅 Vue2 支持)。
  • 特点:适用于简单的父子组件通信,但不建议在大型或复杂的项目中使用,因为它会增加组件间的耦合度。
  • 使用:在子组件中,使用 this.$parent 访问父组件;在父组件中,使用this.$children 访问子组件(但注意$children 是一个数组,需要指定索引)。

父组件

<template><child-component :count="count" @increment="onIncrement"></child-component>
</template><script>import ChildComponent from "./ChildComponent.vue";export default {components: {ChildComponent,},data() {return {count: 100,};},methods: {onIncrement(e) {this.count += e;console.log(this.$children); // undefined},},};
</script>

子组件

<template><div><div>{{ count }}</div><button @click="handleClick">点一下</button></div>
</template><script>export default {props: ["count"],methods: {handleClick() {this.$parent.onIncrement(2);},},};
</script>

注意:

  • 在选项式组件中,无论是在子组件的 template 模板上使用 $emit,还是在<script>中使用 $emit,再或者使用this.$parent访问父组件的属性和方法,请确保子组件只有一个根节点,否则会报警告。
  • $parent 只能用在选项式组件中,子组件使用 this.$parent 访问父组件实例时,父组件也必须是选项式组件,请勿将选项式和组合式语法混用。

四、$refs

  • 概述:通过 $refs 可以访问组件实例或 DOM 元素。
  • 特点:主要用于父子组件之间的直接通信,但也可以用于访问任意组件实例。

选项式

父组件

<template><button @click="giveMoney">给儿子零花钱</button><child-component ref="child"></child-component>
</template><script>import ChildComponent from "./ChildComponent.vue";export default {components: {ChildComponent,},methods: {giveMoney() {this.$refs.child.changeMoney(1000);},},};
</script>

子组件

<template><div>零花钱:{{ money }} 元</div>
</template><script>export default {data() {return {money: 1,};},methods: {changeMoney(n) {this.money += n;},},};
</script>

组合式

父组件

<template><child-componentref="child":count="count"@increment="onIncrement"></child-component><button @click="giveMoney">给儿子零花钱</button>
</template><script setup>import { ref } from "vue";import ChildComponent from "./ChildComponent.vue";const child = ref(null);function giveMoney() {child.value.changeMoney(1000);}
</script>

子组件

<template><div>零花钱:{{ money }} 元</div>
</template><script setup>import { ref } from "vue";const money = ref(1);function changeMoney(n) {money.value += n;}// 子组件的数据和方法只有暴露出去,才能被外部访问到defineExpose({changeMoney,});
</script>

五、Provide / Inject

  • 概述:provideinject 允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深。
  • 特点:跨层级组件通信,但要注意不要滥用,以免造成组件间的耦合度过高。
  • 使用:在祖先组件中,使用 provide() 函数提供数据或方法;在子孙组件中,通过 inject 选项接收这些数据或方法。

祖先组件

<template><div class="parent"><div>总资产:{{ money }} 元</div><child-component></child-component></div>
</template><script setup>import { provide, ref } from "vue";import ChildComponent from "./ChildComponent.vue";const money = ref(1000000000);function giveMoney(n) {money.value -= n;}provide("money", money);provide("giveMoney", giveMoney);
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><div>儿子的零花钱:{{ MyMoney }} 元</div><button @click="handleClick">点一下</button><grand-child></grand-child></div>
</template><script setup>import GrandChild from "./GrandChild.vue";import { ref, inject } from "vue";const MyMoney = ref(1);const parentMoney = inject("money");const getMoney = inject("giveMoney");function handleClick() {if (parentMoney.value > 0) {MyMoney.value += 100;getMoney(100);}}
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件

<template><div class="grand-child"><div>孙子的零花钱:{{ MyMoney }} 元</div><button @click="handleClick">点一下</button></div>
</template><script setup>import { ref, inject } from "vue";const MyMoney = ref(0);const parentMoney = inject("money");const getMoney = inject("giveMoney");function handleClick() {if (parentMoney.value > 0) {MyMoney.value += 1000;getMoney(1000);}}
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

六、$attrs / $listeners

  • $attrs 包含了组件所有透传 attributes。"透传 attribute"指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。
  • $listeners 包含了父作用域中的(不含.native 修饰符) v−on 事件监听器,可以通过 v−on="listeners" 传入内部组件。$listeners 只能在 Vue2 中使用,Vue3 中的透传的事件包含在 $attrs 中。

Vue3 语法

父组件

<template><div class="parent"><div>总资产:{{ money }} 元</div><child-component :money @click="giveMoney"></child-component></div>
</template><script setup>import { ref } from "vue";import ChildComponent from "./ChildComponent.vue";const money = ref(1000000000);function giveMoney(e) {if (e.target) {return;}money.value -= e;}
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><div>子组件</div><grand-child v-bind="$attrs"></grand-child></div>
</template><script setup>import GrandChild from "./GrandChild.vue";
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件

<template><div class="grand-child"><h2>孙组件</h2><div>祖先透传过来的数据: {{ $attrs }}</div><button @click="getMoney">花钱</button></div>
</template><script setup>import { useAttrs } from "vue";const attrs = useAttrs();console.log("孙组件", attrs);function getMoney() {attrs.onClick(1000);}
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

giveMoney 可以作为一个方法而非事件进行传递:

<!-- 祖先组件 -->
<child-component :money :giveMoney></child-component><!-- 孙组件 -->
<button @click="getMoney">花钱</button>function getMoney() { attrs.giveMoney(1000) }

在这里插入图片描述

Vue2 语法

祖先组件

<template><div class="parent"><div>总资产:{{ money }} 元</div><child-component :money="money" @click="giveMoney"></child-component></div>
</template><script>import ChildComponent from "./ChildComponent.vue";export default {components: {ChildComponent,},data() {return {money: 1000000000,};},methods: {giveMoney(e) {if (e.target) {return;}this.money -= e;},},};
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><div>子组件</div><grand-child v-bind="$attrs" v-on="$listeners"></grand-child></div>
</template><script>import GrandChild from "./GrandChild.vue";export default {components: {GrandChild,},mounted() {console.log("子组件", this.$attrs, this.$listeners);},};
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件

<template><div class="grand-child"><h2>孙组件</h2><div>祖先透传过来的数据: {{ $attrs }}</div><button @click="getMoney">花钱</button></div>
</template><script>export default {methods: {getMoney() {this.$listeners.click(1000);},},mounted() {console.log("孙组件", this.$attrs, this.$listeners);},};
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

运行效果同上面的 Vue3 代码。

七、slot 作用域插槽

  • 概述:作用域插槽是插槽的一种特殊形式,它允许子组件将数据传递给插槽内容。这样,父组件就可以在插槽内容中访问子组件的数据,从而实现一种数据传递的效果。
  • 特点:作用域插槽使得组件间的数据传递更加灵活和强大,但也需要更加复杂的实现和理解。

父组件

<template><div class="parent"><h3>父组件</h3><child-component><template #header="headerProps"><div>头部-来自作用域插槽的内容:{{ headerProps }}</div></template><template #default="defaultProps"><div>主体-来自作用域插槽的内容:{{ defaultProps }}</div></template><template #footer="footerProps"><div>底部-来自作用域插槽的内容:{{ footerProps }}</div></template></child-component></div>
</template><script setup>import ChildComponent from "./ChildComponent.vue";
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><h3>子组件</h3><slot name="header" message="hello header"></slot><slot message="hello default"></slot><slot name="footer" message="hello footer"></slot></div>
</template><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

八、全局事件总线(EventBus / mitt)

  • EventBus 通过创建一个空的 Vue 实例作为全局事件总线,在组件间通过它来触发和监听事件。适用于任意组件间的通信,但需要注意事件名的冲突和事件清理。只有 Vue2 支持!
  • mitt 是一个在 Vue.js 应用程序及其他 JavaScript 环境中使用的小型事件总线库。适用于当传统的 props、emits、provide/inject 等方式不足以满足需求的场景。

EventBus

event-bus.js

import Vue from "vue";export const EventBus = new Vue();

祖先组件

<template><div class="parent"><div>总资产:{{ money }} 元</div><button @click="handleClick">给孙子小明零花钱</button><child-component></child-component></div>
</template><script>import ChildComponent from "./ChildComponent.vue";import { EventBus } from "./event-bus";export default {components: {ChildComponent,},data() {return {money: 1000000000,};},methods: {handleClick() {this.money -= 1000;EventBus.$emit("give-money", 1000);},},};
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><div>子组件</div><grand-child></grand-child><grand-child2></grand-child2></div>
</template><script>import GrandChild from "./GrandChild.vue";import GrandChild2 from "./GrandChild2.vue";export default {components: {GrandChild,GrandChild2,},};
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件-小明

<template><div class="grand-child"><h2>孙组件-小明</h2><div>零花钱:{{ money }} 元</div><button @click="handleClick">给妹妹小红零花钱</button></div>
</template><script>import { EventBus } from "./event-bus";export default {data() {return {money: 0,};},methods: {handleClick() {if (this.money > 0) {this.money--;EventBus.$emit("give-sister", 1);}},},mounted() {EventBus.$on("give-money", (value) => {this.money += value;});},beforeDestroy() {EventBus.$off("give-money");},};
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

孙组件-小红

<template><div class="grand-child"><h2>孙组件-小红</h2><div>零花钱:{{ money }} 元</div></div>
</template><script>import { EventBus } from "./event-bus";export default {data() {return {money: 0,};},mounted() {EventBus.$on("give-sister", (value) => {this.money += value;});},beforeDestroy() {EventBus.$off("give-sister");},};
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

在这里插入图片描述

mitt

安装

npm install --save mitt

Emitter.js

import mitt from "mitt";export const Emitter = mitt();

祖先组件

<template><div class="parent"><div>总资产:{{ money }} 元</div><button @click="handleClick">给孙子小明零花钱</button><child-component></child-component></div>
</template><script setup>import ChildComponent from "./ChildComponent.vue";import { ref } from "vue";import { Emitter } from "./emitter";const money = ref(1000000000);function handleClick() {money.value -= 1000;Emitter.emit("give-money", 1000);}
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><h3>子组件</h3><grand-child></grand-child><grand-child2></grand-child2></div>
</template><script setup>import GrandChild from "./GrandChild.vue";import GrandChild2 from "./GrandChild2.vue";
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件-小明

<template><div class="grand-child"><h2>孙组件-小明</h2><div>零花钱:{{ money }} 元</div><button @click="handleClick">给妹妹小红零花钱</button></div>
</template><script setup>import { onBeforeUnmount, onMounted, ref } from "vue";import { Emitter } from "./emitter";const money = ref(0);function handleClick() {if (money.value > 0) {money.value--;Emitter.emit("give-sister", 1);}}onMounted(() => {Emitter.on("give-money", (value) => {money.value += value;});});onBeforeUnmount(() => {Emitter.off("give-money");});
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

孙组件-小红

<template><div class="grand-child"><h2>孙组件-小红</h2><div>零花钱:{{ money }} 元</div></div>
</template><script>import { EventBus } from "./event-bus";export default {data() {return {money: 0,};},mounted() {EventBus.$on("give-sister", (value) => {this.money += value;});},beforeDestroy() {EventBus.$off("give-sister");},};
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

效果同 EventBus。

九、Vuex / Pinia

  • 概述:Vuex 和 Pinia 是 Vue.js 的状态管理模式和库,适用于大型应用,提供全局的状态存储。

Vuex

1.安装 Vuex

npm install vuex@next --save

2.配置 Vuex Store

import { createStore } from "vuex";export default createStore({state() {return {count: 0,};},getters: {doubleCount: (state) => state.count * 2,},mutations: {increment(state) {state.count++;},decrement(state) {state.count--;},},actions: {incrementIfOdd({ commit, state }) {if (state.count % 2 === 1) {commit("increment");}},},
});

3.在 Vue 应用中使用 Vuex Store

import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";const app = createApp(App);app.use(store);
app.mount("#app");

4.在 Vue 组件中使用 Vuex Store

祖先组件

<template><div class="parent"><h3>祖先组件</h3><p>Count: {{ count }}</p><p>Double Count: {{ doubleCount }}</p><button @click="increment">Increment</button><button @click="decrement">Decrement</button><child-component></child-component></div>
</template><script setup>import ChildComponent from "./ChildComponent.vue";import { computed } from "vue";import { useStore } from "vuex";const store = useStore();const count = computed(() => store.state.count);const doubleCount = computed(() => store.getters.doubleCount);function increment() {store.commit("increment");}function decrement() {store.commit("decrement");}
</script><style scoped>.parent {border: 1px solid red;padding: 10px;}
</style>

子组件

<template><div class="child"><h3>子组件</h3><grand-child></grand-child><grand-child2></grand-child2></div>
</template><script setup>import GrandChild from "./GrandChild.vue";import GrandChild2 from "./GrandChild2.vue";
</script><style scoped>.child {border: 1px solid blue;padding: 10px;margin-top: 20px;}
</style>

孙组件-1、孙组件-2

<template><div class="grand-child"><h2>孙组件-1</h2><p>Count: {{ count }}</p><p>Double Count: {{ doubleCount }}</p><button @click="increment">Increment</button><button @click="decrement">Decrement</button></div>
</template><script setup>import { computed } from "vue";import { useStore } from "vuex";const store = useStore();const count = computed(() => store.state.count);const doubleCount = computed(() => store.getters.doubleCount);function increment() {store.commit("increment");}function decrement() {store.commit("decrement");}
</script><style scoped>.grand-child {border: 1px solid green;padding: 10px;margin-top: 20px;}
</style>

在这里插入图片描述

Pinia

1.安装 Pinia

npm install pinia --save

2.配置 Pinia store

// src/stores/counter.jsimport { defineStore } from "pinia";export const useCounterStore = defineStore("counter", {state: () => ({count: 0,}),getters: {doubleCount: (state) => state.count * 2,},actions: {increment() {this.count++;},decrement() {this.count--;},},
});

3.在 Vue 应用中使用 Pinia Store

// src/main.jsimport { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";const app = createApp(App);
const pinia = createPinia();app.use(pinia);
app.mount("#app");

4.在 Vue 组件中使用 Pinia Store

祖先组件

<template><div class="parent"><h3>祖先组件</h3><p>Count: {{ counterStore.count }}</p><p>Double Count: {{ counterStore.doubleCount }}</p><button @click="counterStore.increment">Increment</button><button @click="counterStore.decrement">Decrement</button><child-component></child-component></div>
</template><script setup>import ChildComponent from "./ChildComponent.vue";import { useCounterStore } from "@/stores/counter";const counterStore = useCounterStore();
</script><style scoped>.parent {border: 1px solid redpadding: 10px}
</style>

子组件

<template><div class="child"><h3>子组件</h3><grand-child></grand-child><grand-child2></grand-child2></div>
</template><script setup>import GrandChild from "./GrandChild.vue";import GrandChild2 from "./GrandChild2.vue";
</script><style scoped>.child {border: 1px solid bluepadding: 10pxmargin-top: 20px}
</style>

孙组件-1、孙组件-2

<template><div class="grand-child"><h2>孙组件</h2><p>Count: {{ counterStore.count }}</p><p>Double Count: {{ counterStore.doubleCount }}</p><button @click="counterStore.increment">Increment</button><button @click="counterStore.decrement">Decrement</button></div>
</template><script setup>import { useCounterStore } from "@/stores/counter";const counterStore = useCounterStore();
</script><style scoped>.grand-child {border: 1px solid greenpadding: 10pxmargin-top: 20px}
</style>

十、本地存储(LocalStorage/SessionStorage)

  • 概述:利用浏览器的本地存储功能,实现跨会话或跨标签页的组件通信。
  • 特点:适用于需要持久化数据的场景,但不适用于实时性要求较高的通信。同样也不适用于在同一页面多个组件共享数据的情形,因为某一组件更改了本地存储的数据,并不能响应式地更新相关组件的数据和视图。

好了,分享结束,谢谢点赞,下期再见。


http://www.mrgr.cn/news/18820.html

相关文章:

  • wacat - 一款开源随机测试工具
  • AI 生成技术引领创新潮流,多领域应用展现广阔前景
  • vue3中播放m3u8,附测试网址
  • 涨薪秘籍?40w年薪项目经理力荐,10个项目管理神器大放送!
  • quartz源码-Schedule启动过程分析
  • Java算法之LRUCache缓存实现
  • JVM面试(三)类加载过程
  • 人该怎样活着呢?48
  • 深度学习引介:未来已来
  • CMake编译测试
  • 15年期权停交易的时候究竟发生了什么?期权零门槛开户怎么做?
  • ​​​​​​​《黑神话:悟空》—— 高科技点亮西游神话璀璨之路
  • 免费pdf转word软件,为你整理出8种方法,总有一个适合你
  • DRY原则-用函数和模块化来避免重复代码
  • 2024自动化测试面试真题(附答案)!
  • 分类预测|基于雪消融优化极端梯度提升的数据分类预测Matlab程序SAO-XGBoost 多特征输入多类别输出
  • 【干货】深度学习调参秘籍【表格】
  • 企业数据管理方案-提升效率与决策力的关键
  • 使用Spring Boot拦截器实现时间戳校验以防止接口被恶意刷
  • 《重生到现代之从零开始的C语言生活》—— 指针7