Vue3如何优雅的跨组件通信

在这里插入图片描述

开发中经常会遇到跨组件通信的场景。props 逐层传递的方法实在是太不优雅了,所以今天总结下可以更加简单的跨组件通信的一些方法。
请添加图片描述

依赖注入
请添加图片描述

<!-- App.vue -->
<script setup lang="ts">
import {
    ref, provide } from "vue";
import Child from "./components/Child.vue";
const count = ref(0)
const updateCount = () => count.value ++
provide("count", {
   count, updateCount})
</script>

<template>
  <h4>XXXX</h4>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">change</button>
  <Child />
</template>
<!-- Child.vue -->
<template>
  <Other />
</template>
  
<script setup lang='ts'>
import Other from "./other.vue"
</script>
<!-- other.vue -->
<template>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">other change</button>
</template>
  
<script setup lang='ts'>
import {
    inject } from "vue"
const {
   count, updateCount} = inject("count")
</script>

setup 组件中,使用 inject 跨组件通信是最佳的方案。所以该模式下,是没有提供event bus 事件总线。

但是在 option api 模式下,还需要额外的注册,显的有点麻烦。

<script lang='ts'>
export default {
   
  emits: ["some-name"]
}
</script>

属性透传

<!-- App.vue -->
<script setup lang="ts">
import {
    ref, provide } from "vue";
import Attr from "./components/Attr.vue";
const count = ref(0)
const updateCount = () => count.value ++
provide("count", {
   count, updateCount})
</script>

<template>
  <h4>XXXX</h4>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">change</button>
  <Attr :count="count" :updateCount="updateCount" />
</template>
<!-- Attr.vue -->
<template>
  <div>attr component</div>
  <Child v-bind="$attrs" />
</template>
  
<script setup lang='ts'>
import Child from './Child.vue';
</script>

属性透传这种方式类似于react中手动透传属性。感觉有点暴力,但是又特别方便快捷。

function App (props) {
   
  return <Other {
   ...props} />
}

Vue中默认透传的属性有 style、class、key。如果子组件也存在class、style,则会自动合并class、style。

如果你的子组件是根组件时,可以省略 v-bind="$attrs"

<template>
  <Child />
</template>

状态库
状态管理库我们以Pinia为例。

<!-- App.vue -->
<script setup lang="ts">
import Other from "./components/Other.vue";
import {
    useCounterStore } from "./store/index"
const state = useCounterStore()
</script>

<template>
  <h4>XXXX</h4>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">change</button>
  <Other />
</template>
import {
    defineStore } from "pinia" 
import {
    ref } from "vue"

export const useCounterStore = defineStore('counter', () => {
   
  const count = ref(0)
  function updateCount() {
   
    count.value++
  }

  return {
    count, updateCount }
})
<!-- Other.vue -->
<template>
  <div>pinia store</div>
  <div>{
   {
    state.count }}</div>
  <button @click="state.updateCount">other change</button>
</template>
  
<script setup lang='ts'>
import {
    useCounterStore } from '../store';
const state = useCounterStore()
</script>

状态管理库最大的缺点是,没法使用解构语法。因为这会导致失去响应式的能力。

事件总线

事件总线(event bus)比较特殊,因为在组合式API里不支持该方式,所以下面的例子适合 Option API 组件。

<!-- App.vue -->
<script setup lang="ts">
import {
    ref } from "vue";
import Other from "./components/Other.vue";
const count = ref(0)
const updateCount = () => count.value ++
</script>

<template>
  <h4>XXXX</h4>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">change</button>
  <Other @updateCount="updateCount()" />
</template>
<!-- Other.vue -->
<template>
  <div>eventBus store</div>
  <button @click="$emit('updateCount')">other change</button>
</template>
  
<script lang='ts'>
export default {
   
  emits: ["updateCount"]
}
</script>

事件总线更适合传递事件。

自定义事件

但是有时候,你可能非常想使用事件总线的方式在 setup 组件中传递事件,这时候我们可以使用自定义的事件的方式实现这种功能。

class EventBus {
   
  constructor() {
   
    this.events = {
   };
  }

  // 订阅事件
  on(eventName, callback) {
   
    if (!this.events[eventName]) {
   
      this.events[eventName] = [];
    }
    this.events[eventName].push(callback);
  }

  // 发布事件
  emit(eventName, eventData) {
   
    const eventCallbacks = this.events[eventName];
    if (eventCallbacks) {
   
      eventCallbacks.forEach(callback => {
   
        callback(eventData);
      });
    }
  }

  // 取消订阅事件
  off(eventName, callback) {
   
    const eventCallbacks = this.events[eventName];
    if (eventCallbacks) {
   
      this.events[eventName] = eventCallbacks.filter(cb => cb !== callback);
    }
  }
}

export const eventBus = new EventBus()
<!-- App.vue -->
<script setup lang="ts">
import {
    ref } from "vue";
import Other from "./components/Other.vue";
import {
    eventBus } from "./store/eventBus";
const count = ref(0)
const updateCount = () => count.value ++
eventBus.on("updateCount", updateCount)
</script>

<template>
  <h4>XXXX</h4>
  <div>{
   {
    count }}</div>
  <button @click="updateCount">change</button>
  <Other @updateCount="updateCount()" />
</template>
<!-- Other.vue -->
<template>
  <div>eventBus</div>
  <button @click="eventBus.emit('updateCount', null)">other change</button>
</template>
  
<script setup lang='ts'>
import {
    eventBus } from "../store/eventBus";
</script>

当然,我们这里不止可以使用 event bus,发布订阅模式也很适合。可以参考我以前的设计模式的文章实现这个功能。

总结
每种方式都有自己的优点和缺点,根据使用场景选择最合适的才能算是最优的方案。

相关推荐

  1. vue3组件(多组件)通信:事件总线【Event Bus】

    2023-12-08 13:40:08       34 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-08 13:40:08       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-08 13:40:08       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-08 13:40:08       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-08 13:40:08       18 阅读

热门阅读

  1. QT作业1

    QT作业1

    2023-12-08 13:40:08      38 阅读
  2. Spring Task

    2023-12-08 13:40:08       39 阅读
  3. Swagger PHP Thinkphp 接口文档

    2023-12-08 13:40:08       47 阅读
  4. 一、C#笔记

    2023-12-08 13:40:08       34 阅读
  5. QT学习随记 (二)—— QT基础

    2023-12-08 13:40:08       38 阅读
  6. 一起学习云计算

    2023-12-08 13:40:08       36 阅读
  7. 我的创作纪念日

    2023-12-08 13:40:08       51 阅读
  8. Ubuntu环境下使用nginx实现强制下载静态资源

    2023-12-08 13:40:08       37 阅读
  9. Ubuntu开机出现Welcome to emergency mode解决办法

    2023-12-08 13:40:08       58 阅读
  10. Appium:iOS部署

    2023-12-08 13:40:08       35 阅读