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、本地存储等方法进行通信。

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

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


参考文档

相关推荐
Myli_ing2 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风4 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
PandaCave11 分钟前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟13 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾35 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧43 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
chusheng18401 小时前
Java项目-基于SpringBoot+vue的租房网站设计与实现
java·vue.js·spring boot·租房·租房网站
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架