前言
在一个 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
provide
和 inject
需要配合使用。父组件通过 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>
非父子组件通信
非父子组件由于不存在嵌套关系,所以一般不能使用以上四种方式,而是把数据或者方法存放在所有组件都能访问到的地方,比如状态管理,本地存储等。所以这些方法也同时可以在父子组件之间使用。
状态管理 vuex
或 pinia
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 执行一些对应的操作。
通过第三方库,比如 mitt 或 tiny-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>
通过本地存储 localStorage
、sessionStorage
或 Cookies
该方法不受框架限制,你可以在任意框架下使用该方法,和使用状态管理的原理类似,将数据存储在公共区域。区别是,本地存储的数据在组件中不能直接进行监听,需要写一些额外的方法。这里不做过多介绍。
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)
})
},
}
总结
总的来说,组件通信有以下几种方法:
-
父子组件可以通过
props
、emits
、ref
、provide/inject
等方法进行通信。 -
非父子组件可以通过
pinia
、第三方库mitt
、本地存储等方法进行通信。
在实际开发中请结合实际情况进行选择。
希望以上内容对你有帮助,如果你还有其他方法,欢迎补充。