组件通讯
组件与组件间常出现的关系:
族谱📜:
- 父子关系:A和AA,AA和AAA,B和BB、BB和BBB
- 兄弟关系:A和B
- 爷孙关系:A和AAA、B和BBB
- 邻居关系:A和B内任意,B与A内任意
常用的通讯方式
名称 | 说明 |
---|---|
Prop 和 $emit | 单向数据流,官方推荐,最基础也是最常用的传输手段 |
eventBus | 事件总线,耦合低,但是用多了会让业务代码分散到各处,需要另外定制一些团队规则 |
vuex | 官方出品的全局状态管理库 |
pinia | 新一代的vue状态管理(2021年末) |
不常用的通讯方式(不推荐)
名称 | 说明 |
---|---|
$parent 、 $children | 直接访问父子实例上的方法或者属性,感觉比较不安全,少用 |
$refs | 用来操作DOM常用,其实也可以用来跟上下文的组件通讯,也是少用 |
provide 、 inject | 2.2 新增,官方不建议用在日常业务组件中,建议用在组件库封装时 |
$attrs 、 $listeners、inheritAttrs | 2.4 新增,官方推荐在一些组件封装的地方使用,日常用会有重复触发事件的坑,需要去重 |
PS:虽然方法有这么多,但是团队中每次开发项目应该统一指定一种到两种主要通讯方式,这样方便后续项目维护和升级。
不推荐的方式有的是淘汰的,有的是不便于团队后期维护的,用是能用,但是多少有点门槛或者要给项目带来额外的维护成本。
1、Prop 、 $emit
单向数据流
父组件通过绑定同名的props参数,将数据向子组件传递,通过监听子组件的emit触发的事件来更新自身数据,这个过程是单向的,也称为单向数据流
vue2
<template lang="pug">
Child1(v-bind:param1="param1")
</template>
<script>
import Vue from "Vue"
let parent = new Vue({
el:'#app',
name:"parent",
data(){
return{
param1:"1",
param2:"2"
}
},
method:{
// 父组件修改数据暴露的接口
parentChangeData({param, newValue}){
this[param] = newValue
}
}
components:{
'Child1':{
props:['param1'],
template:`<div>子组件:{{param1}}</div>`,
method:{
changeData(){
// 使用emit触发父组件暴露的方法来修改数据
this.$emit('parentChangeData',{})
}
}
}
}
})
</script>
vue3
<template>
<button
class="cursor-pointer text-black hover:text-sky-400"
@click="$emit('on-menu-click', menu)"
></button>
</template>
<script setup lang="ts">
</script>
2、$parent 、 $children
如何使用
<template lang="pug">
Child1(v-bind:param1="param1")
</template>
export default {
name:"parent-component",
data(){
return{
parentData:"data",
}
},
components:{
'Child1':{
template:`<div>子组件:{{param1}}</div>`,
beforeMount(){
this.getParentData()
},
data (){
return{
childData:"",
}
},
method:{
getParentData(){
this.childData = this.$parent.parentData
}
}
}
}
}
注意事项
- 渲染的时候先子组件的
mounted
,然后再是自己的mounted
要是想在子组件里获取父组件的元素之类的,必须使用nextTick
$children
并不保证顺序,也不是响应式的。**如果你发现自己正在尝试使用$children
来进行数据绑定,考虑使用一个数组配合v-for
来生成子组件,并且使用 Array 作为真正的来源。
适用场景
- 封装嵌套组件时,直接使用长辈或者子孙组件的方法,该方法并不改变数据,常常结合
$broadcast/$dispatch
使用(官方已弃用)
3、$attrs 、 $listeners、inheritAttrs
vue3移除$listeners
场景限制
2.4
二次封装
参阅资料:Vue使用$attrs与$listeners实现多层嵌套传递
用法
<component v-bind="$attrs" v-on="$listeners"></component>
传统父组件要传递属性给孙组件需要先经过子组件定义props来逐层传递,在高层级的业务场景下非常不方便,而使用$attrs可以做到父组件直接传递到下级多层组件中,大大减少了层级带来的逐层传递问题关系。
<template lang="pug">
Child1(v-bind:param1="param1")
</template>
<script>
/* 顶层组件 */
let parent = new Vue({
el:'#app',
name:"parent",
data(){
return{
param1:"1",
param2:"2",
}
},
methods:{
// 顶层组件暴露一个修改属性的方法
changeData(newData){
this.param2 = newData
}
}
/* 中层组件 */
components:{
'Child1':{
// 中层组件不需重复定义props,只需要添加固定的 v-bind="$arrts"、v-on="$listeners"
template:`<Child2 v-bind="$arrts" v-on="$listeners"></Child2>`,
/* 下层组件 */
components:{
'Child2':{
props:['param2'] // 下层组件依然就能得到param2,和触发changeData
template:`<div>我是孙组件我们直接使用param1 {{param1}}</div>`,
methods:{
setParentData(){
// 下层组件直接能使用 $emit 来触发顶层组件的方法
this.$emit('changeData', this.param2)
}
}
}
}
}
}
})
</script>
坑
4、provide 和 inject (2.2 加入)
- 父组件
// 父组件通过provide字段定义事件
export default {
mounted() {},
data:(){},
provide: { 'eventName':()=>{}, 'data':'ccvb'}
}
- 子组件
// 子组件通过 inject调用
export default {
// 通过 this+时间名称调用,支持函数或者变量
created(){
this.eventName()
},
inject: ['eventName', 'data']
}
5、$refs 操作组件实例
6、eventBus 事件总线(推荐)
事件总线是官方推荐的其从一套组件通讯解决方案:点我跳转()
vue2
// @/eventBus.ts
import Vue from 'vue'
export default new Vue()
import eventBus from '@libs/eventBus'
export default {
mounted() {
// 在组件创建时,添加一个名为 `hello` 的事件侦听
eventBus.$on('hello', () => {
console.log('Hello World')
})
},
beforeDestroy() {
// 在组件销毁前,通过 `hello` 这个名称移除该事件侦听
eventBus.$off('hello')
},
}
import eventBus from './eventBus'
export default {
methods: {
sayHello() {
// 触发名为 `hello` 的事件
eventBus.$emit('hello')
},
},
}
vue3
使用第三方时间管理库mitt
// src/libs/eventBus.ts
import mitt from 'mitt'
export default mitt()
import { defineComponent, onBeforeUnmount } from 'vue'
import eventBus from '@libs/eventBus'
export default defineComponent({
setup() {
// 声明一个打招呼的方法
function sayHi(msg = 'Hello World!') {
console.log(msg)
}
// 启用侦听
eventBus.on('sayHi', sayHi)
// 在组件卸载之前移除侦听
onBeforeUnmount(() => {
eventBus.off('sayHi', sayHi)
})
},
})
import { defineComponent } from 'vue'
import eventBus from '@libs/eventBus'
export default defineComponent({
setup() {
// 调用打招呼事件,传入消息内容
eventBus.emit('sayHi', 'Hello')
},
})
reactive(适用于一些临时页面,临时组件)
兄弟组件间或者组件内部多层级数据状态共享时,适合采用
// @/xxxState.ts
import { reactive } from 'vue';
// 定义为唯一导出
export default reactive({
// 设置一个属性并赋予初始值
message: 'Hello World',
// 添加一个更新数据的方法
setMessage(msg: string) {
this.message = msg;
},
});
// 具名导出,相当于一个极简单的全局状态管理
export const xxxxStroe = reactive({})
// xxx.vue
import { defineComponent, watch } from 'vue'
import { state } from '@/xxxState'
export default defineComponent({
setup() {
console.log(state.message)
// Hello World
// 因为是响应式数据,所以可以侦听数据变化
watch(
() => state.message,
(val) => {
console.log('Message 发生变化:', val)
}
)
setTimeout(() => {
state.setMessage('Hello Hello')
// Message 发生变化: Hello Hello
}, 1000)
setTimeout(() => {
state.message = 'Hi Hi'
// Message 发生变化: Hi Hi
}, 2000)
},
})
优点:
- 使用简单,轻量
- 合适使用在一个组件或者插件中,进行局部的状态管理
缺点:
- 难以追踪,不合适用来全局,会增加维护成本
7、vuex
总结