Vue 3 组件之间如何通信

前言

在一个 Vue 项目中,每一个 .vue 文件都可以被视为一个组件,组件之间可以相互嵌套,相互组合,这在实际开发中是非常常见的,比如以下结构:

html 复制代码
<template>
  <TheHeader />
  <main>
    <TheContent />
    <TheAside />
  </main>
</template>

其中,TheHeader、TheContent 以及 TheAside 都属于当前组件的子组件,他们三个同时又可以视为彼此的兄弟组件。

那么组件与组件之间如何进行通信(传递数据,触发事件),以及都有哪些方式,下面我们就来一一介绍。

由于 Vue 3 提供了两种书写风格,组合式 API 和 选项式 API,所以本文也会同时提供两种写法。

父子组件通信

props

props 代表参数,子组件可以通过 props 属性设置和接收参数,父组件在调用时传递即可。

选项式 API

子组件:

html 复制代码
<script>
  export default {
    props: {
      greetingMessage: String,
    },
  }
</script>

父组件:

html 复制代码
<MyComponent greeting-message="hello" />

组合式 API

子组件:

html 复制代码
<script setup>
  const props = defineProps(["greetingMessage"])
</script>

父组件:

html 复制代码
<MyComponent greeting-message="hello" />

emits

子组件可以通过 emits 属性定义一些事件,在需要的时候触发,父组件通过 v-on@ 监听即可。

选项式 API

子组件:

html 复制代码
<script>
  export default {
    emits: ["check"],

    created() {
      this.$emit("check")
    },
  }
</script>

父组件:

html 复制代码
<MyComponent @check="onCheck" />

组合式 API

子组件:

html 复制代码
<script setup>
  const emits = defineEmits(["check"])

  emits("check")
</script>

父组件:

html 复制代码
<MyComponent @check="onCehck" />

ref

通过给子组件设置 ref 属性,父组件可以访问子组件并调用子组件暴露的方法。

选项式 API

选项式 API 默认会暴露所有属性和方法,无需额外的操作。

子组件:

html 复制代码
<script>
  export default {
    methods: {
      check() {
        console.log("check")
      },
    },
  }
</script>

父组件:

html 复制代码
<MyComponent ref="MyComponent" />

<script>
  export default {
    mounted() {
      this.$refs.MyComponent.check() // check
    },
  }
</script>

组合式 API

组合式 API 需要通过 defineExpose 显式的暴露指定的方法,才能被其他组件访问。

子组件:

html 复制代码
<script setup>
  function check() {
    console.log("check")
  }

  defineExpose({ check })
</script>

父组件:

html 复制代码
<MyComponent ref="MyComponent" />

<script setup>
  import { ref, onMounted } from "vue"

  const MyComponent = ref()

  onMounted(() => {
    MyComponent.value.check() // check
  })
</script>

provide 和 inject

provideinject 需要配合使用。父组件通过 provide 提供一个值,后代组件(包括子组件、孙组件......)通过 inject 获取这个值。该方法可以跨越层级使用。

选项式 API

父组件:

html 复制代码
<script>
  export default {
    provide: {
      foo: "123",
    },
  }
</script>

后代组件:

html 复制代码
<script>
  export default {
    inject: ["foo"],
    created() {
      console.log(this.foo) // 123
    },
  }
</script>

组合式 API

父组件:

html 复制代码
<script setup>
  import { provide } from "vue"

  provide("foo", 123)
</script>

后代组件:

html 复制代码
<script setup>
  import { inject } from "vue"

  const foo = inject("foo")

  console.log(foo) // 123
</script>

非父子组件通信

非父子组件由于不存在嵌套关系,所以一般不能使用以上四种方式,而是把数据或者方法存放在所有组件都能访问到的地方,比如状态管理,本地存储等。所以这些方法也同时可以在父子组件之间使用。

状态管理 vuexpinia

pinia 就是 vuex 的第五个版本,Vue 团队给他换了个马甲,为啥改名看这里。这里就以 pinia 为例。

首先需要定义 store:

js 复制代码
// /src/stores/my-store.js
import { defineStore } from "pinia"
import { reactive } from "vue"

export default defineStore("my-store-id", () => {
  const state = reactive({
    isShow: true,
  })

  return { state }
})

组件 A:

html 复制代码
<script setup>
  import useTransportStore from "@/stores/my-store"

  const { state } = useTransportStore()

  console.log(state.isShow) // true

  setTimeout(() => {
    state.isShow = false
  }, 2000)
</script>

组件 B:

html 复制代码
<script setup>
  import { watch } from "vue"
  import useTransportStore from "@/stores/my-store"

  const { state } = useTransportStore()

  console.log(state.isShow) // true

  watch(state.isShow, val => {
    console.log(val) // false
  })
</script>

由于在 store 中使用 reactive 进行了代理,所以即使任一组件对 state 进行修改,也都可以随时获取到最新的值,并且可以通过 watch 执行一些对应的操作。

通过第三方库,比如 mitttiny-emitter

在 Vue 3.x 中,EventBus 已经不再被支持,因为 $on(还有 $off$once)方法已经被移除了,为什么移除看这里。取而代之的是,Vue 官方推荐使用 mitt 和 tiny-emitter 进行通信。

可能有人担心安装第三方库会不会增加打包体积,这里告诉大家,不用担心,mitt 压缩后只有 200 字节,极其特别的小。并且这两个包都不受框架限制。除了在 Vue 中使用,你还可以在 React 或者 nodejs 中使用。

这里以 mitt 为例(mitt 翻译为手套,为啥叫手套咱也不知道,可能和 emit 比较接近?)。

先安装一下:

bash 复制代码
npm i mitt

引入:

js 复制代码
// /src/utils/mitt.js
import mitt from "mitt"

const emitter = mitt()

export default emitter

组件 A:

html 复制代码
<script setup>
  import emitter from "@/utils/mitt"

  emitter.emit("check")
</script>

组件 B:

html 复制代码
<script setup>
  import emitter from "@/utils/mitt"

  emitter.on("check", () => {
    console.log("check")
  })
</script>

通过本地存储 localStoragesessionStorageCookies

该方法不受框架限制,你可以在任意框架下使用该方法,和使用状态管理的原理类似,将数据存储在公共区域。区别是,本地存储的数据在组件中不能直接进行监听,需要写一些额外的方法。这里不做过多介绍。

Vue 2 还可通过事件总线 EventBus

上面已经提到,该方法在 Vue 3.x 已经不能直接使用了,推荐使用前面的几种方法。但这里还是写一下,算是回忆一下。

导出一个 Vue 实例:

js 复制代码
// /src/utils/event-bus.js
import Vue from "vue"
export default new Vue()

组件 A:

js 复制代码
import Bus from "event-bus.js"

export default {
  methods: {
    handleClick(val) {
      Bus.$emit("functionName", val)
    },
  },
}

组件 B:

js 复制代码
import Bus from "event-bus.js"

export default {
  created() {
    Bus.$on("functionName", val => {
      console.log(val)
    })
  },
}

总结

总的来说,组件通信有以下几种方法:

  • 父子组件可以通过 propsemitsrefprovide/inject 等方法进行通信。

  • 非父子组件可以通过 pinia 、第三方库 mitt、本地存储等方法进行通信。

在实际开发中请结合实际情况进行选择。

希望以上内容对你有帮助,如果你还有其他方法,欢迎补充。


参考文档

相关推荐
zqx_714 分钟前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己30 分钟前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色1 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2341 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普2 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H2 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
花花鱼2 小时前
@antv/x6 导出图片下载,或者导出图片为base64由后端去处理。
vue.js
程序员-珍2 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发