前言
对于Vue来说组件的数据通信非常重要,并且Vue 组件通信在面试中频繁出现,那么组件之间如何进行数据通信的呢?为了更加深入了解组件的数据通信,本文专门总结梳理了一下组件之间通信的场景以及通信的方式以及实现。
组件通信大致有以下几种场景:
- 父子组件通信
- 隔代组件通信
- 兄弟组件通信
- 跨路由组件通信
针对不同的使用场景,如何选择有效的通信方式来实现组件之间的数据通信;
接下来本文将介绍以下七种组件之间的通信方式。
一、props/emit传参
使用介绍
props/emit传参是最基础的组件通信方式,父组件通过props可以向子组件进行通信,子组件通过emit向父组件进行通信。
使用场景
- 父子组件通信
使用方法
1. 父组件向子组件传参
通过在子组件中定义props参数,父组件传入子组件中定义的参数属性来实现通信。
## Vue 3.x
// 父组件
<template>
<child :message="msg"></child>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const msg = ref('hello vue3.x')
</script>
// 子组件
<template>
<div>
<span>{{ message }}</span>
</div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
message: {
type: String
}
})
</script>
## Vue 2.x
// 父组件
<template>
<child :message="message"></child>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
message: 'hello vue2.x'
}
}
}
</script>
// 子组件
<template>
<div>
<span>{{message}}</span>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
default: 'test'
}
}
}
</script>
2.子组件向父组件传参
Vue通过emit的方式来实现子组件向父组件传参,在子组件中使用$emit绑定一个自定义事件, 当这个执行这个语句时,就会将参数传递给父组件;父组件通过$on来监听子组件自定义的事件来获取子组件传递的参数。
## vue3.x
// 父组件
<template>
count:{{num}}
<child :num="num" @addCount="addCount"></child>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const num = ref(100)
function addCount(res) {
num.value = res
}
</script>
// 子组件
<template>
<div>
<button type="primary" @click="addCount">count++</button>
</div>
</template>
<script setup>
const props = defineProps({
num: {
type: Number,
default: 0
}
})
const emit = defineEmits(['addCount'])
function addCount() {
emit('addCount', props.num + 1)
}
</script>
## vue2.x
// 父组件
<template>
count:{{num}}
<child :num="num" @addCount="addCount"></child>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
num: 100
}
},
methods:{
addCount(res) {
this.num = res
}
}
}
</script>
// 子组件
<template>
<div>
<button type="primary" @click="addCount">count++</button>
</div>
</template>
<script>
export default {
props: {
num: {
type: Number,
default: 0
}
},
methods:{
addCount() {
this.$emit('addCount', this.num + 1)
}
}
}
</script>
使用总结
1. 最常见的父子组件通信方式。
2. props支持参数验证。
3. emit只会触发父组件的监听事件。
4. 不适合多层次组件参数传递,需要逐层传递参数。
二、eventBus
使用介绍
EventBus又称为事件总线,可以使用它来进行组件之间通信。其实和vuex还是有些类似的,相当于所有组件共用一个事件中心,这个事件中心用来管理事件,当我们需要用到的时候就向事件中心发送或者接受事件。通过共享一个vue实例,使用该实例的$on以及$emit实现数据传递。
使用场景
- 隔代组件通信
- 兄弟组件通信
使用方法
## vue3.x
Vue3 从实例中完全删除了 $on、$off 和 $once 方法,所以Vue3不支持eventBus。
官方推荐使用第三方库:mitt 、tiny-emitter
## vue2.x
// eventBus.js
const eventBus = new Vue()
export default eventBus
// A 组件
import eventBus from './eventBus'
// 通过emit发送消息
eventBus.$emit('message')
// B组件
import eventBus from './eventBus'
// 通过$on接收消息
eventBus.$on('message', () => {
console.log('收到消息!')
})
// 组件销毁时需要解绑监听
beforeDestroy () {\
eventBus.$off('message')\
}
使用总结
1. 常用于多层嵌套组件场景下兄弟组件或任意两个组件之间通讯。
2. $on事件是不会自动清楚销毁的,需要我们手动来销毁,我们可以在beforeDestroy中解绑监听,避免重复触发。
3. 适合简单场景下使用,太过复杂的场景建议使用vuex。
三、vuex
使用介绍
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态, 并以相应的规则保证状态以一种可预测的方式发生变化。
Vuex包含以下几个部分:全局唯一的状态管理仓库(state),同步操作(mutations)、异步操作(actions)、缓存依赖(getters),模块(modules)。
具体使用方法克访问官网查看》》Vuex官网
使用场景
- 隔代组件通信
- 兄弟组件通信
- 跨路由组件通信
使用方法
// 创建store对象
import Vue from 'vue';
import Vuex from 'vuex';
import persistedState from 'vuex-persistedstate'
Vue.use(Vuex);
const store = new Vuex.Store({
state:{
name:'',
age:0
},
// 数据过滤缓存依赖
getters:{
getUserInfo(state){
return `name=${state.name},age=${state.age}`
}
},
// 同步操作
mutation:{
SET_NAME: (state, name) => {
state.name = name
},
SET_AGE: (state, age) => {
state.age = age
}
},
// 异步操作
actions:{
setName({ commit }, name) {
commit('SET_NAME', name)
}
},
// 数据状态持久化插件,防止页面刷新数据丢失
plugins: [persistedState({ storage: window.sessionStorage })]
})
export default store
## 组件中使用方法
// 同步请求
this.$store.commit('我是菜鸟')
// 异步请求
this.$store.dispatch('setName', '我是菜鸟')
// getter
this.$store.getters.getUserInfo
使用总结
1. 任意组件之间通信,多组件之间通信。
2. 跨路由组件之间通信。
3. 刷新浏览器,vuex数据会丢失,可以采用vuex-persistedstate插件解决此问题。
4. 适合场景复杂的大型项目,简单的业务场景不建议使用。
四、ref
使用介绍
通过$refs可以拿到子组件的实例,从而调用实例里的方法来实现父子组件通信。
使用场景
- 父子组件通信
使用方法
// 父组件
<template>
<div id="app">
count:{{ num }}
<button @click="handle">通过ref获取子组件实例</button>
<child ref="child"></child>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: {
Child
},
data() {
return {
num: 100
};
},
methods: {
handle() {
this.num = this.$refs.child.num;
}
}
}
</script>
// 子组件
<template>
<div>
{{ num }}
<button type="primary" @click="addCount">count++</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
};
},
methods: {
addCount() {
this.num++;
},
getNum() {
return this.num;
}
}
};
</script>
使用总结
1. 可以通过此方式拿到子组件实例,从而拿到子组件实例中的所有属性或调用子组件的事件。
五、provide / inject
### 使用介绍 常用于多层嵌套组件封装,当后代组件需要用到顶层组件的数据方法时可以使用这种方式。在父组件上通过provide来提供后代组件需要使用的数据方法,后代组件通过inject来接收顶层组件传递的数据方法。
使用场景
- 隔代组件通信
使用方法
// 顶层组件
<template>
<div id="app">
<button @click="handle">点击</button>
<child ref="child"></child>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
components: {
Child
},
// 提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。
provide() {
return {
foo: this.paramData // 传过去的必须是可监听的对象,注意,是对象,其他类型都不行
};
},
data() {
return {
paramData: {
color: "red"
}
};
},
methods: {
handle() {
this.paramData.color = "blue";
}
}
};
</script>
// 后代组件
<template>
<div>
{{ foo.color }}
</div>
</template>
<script>
export default {
inject: ["foo"]
};
</script>
使用总结
1. 跨层级组件之间通信。
2. 传递的属性是非响应的。
provide: {
color: "green"
}
3. 如果需要传递响应属性,采用函数的方式传入对象,color:值为响应式对象
provide() {
return {
color: this.paramData,
};
},
data() {
return {
paramData: {
color: "red"
}
};
}
4. 复杂组件中不建议使用此方式传参,任意层级都能访问导致数据追踪比较困难。
六、attrs/listeners
使用介绍
组件嵌套组件时可以使用 attrs/listeners 来支持更多的属性参数传递,例如对elementUI的组件进行二次封装时,就可以通过此方式把多余的属性传递给子组件。
$attrs 可以获取父组件传进来但没有通过props接收的属性
$listeners 定义的事件都在子组件的根元素上,有时候想加到其他元素上。就可以使用 $listerners。它包含了父组件中的事件监听器(除了带有 .native 修饰符的监听器)
使用场景
- 父子组件通信
使用方法
// 顶层组件
<template>
<div id="app">
{{ "顶层组件参数aa:" + aa }}
<child :aa="aa" bb="bb" cc="cc" @parentClick="handle"></child>
</div>
</template>
<script>
import Child from "./Child1.vue";
export default {
components: {
Child
},
data() {
return {
aa: "aa"
};
},
methods: {
handle(data) {
this.aa = data;
}
}
};
</script>
// 子组件
<template>
<div>
<p>子组件prop参数[aa]: {{ aa }}</p>
<p>子组件非prop参数[$attrs]: {{ $attrs }}</p>
<child :cc="$attrs.cc" v-on="$listeners"></child>
</div>
</template>
<script>
import Child from "./Child2.vue";
export default {
components: {
Child
},
props: {
aa: String
}
};
</script>
// 孙子组件
<template>
<div>
<p>孙子组件prop参数[cc]: {{ cc }}</p>
<button @click="handle">点击传参顶层组件</button>
</div>
</template>
<script>
export default {
name: "c1",
props: {
cc: String
},
methods: {
handle() {
this.$emit("parentClick", "子组件传参");
}
}
}
</script>
使用总结
1. 多级组件嵌套需要传递数据时,如果参数过多并且仅仅是传递数据时,可以通过$attr将父组件传递
的未经过prop定义的参数传递给孙子组件。
2. 可以通过$listeners将父组件函数传递给子组件,子组件可以通过emit触发传递参数。
七、$children / $parent
使用介绍
通过children可以拿到父子组件的实例,从而调用实例里的数据方法,实现父子组件通信(并不推荐)。
使用场景
暂时没用过此方式
使用方法
// 获取父组件实例
this.$parent
// 获取子组件实例
this.$children
// 获取根组件实例
this.$root
使用总结
暂时没用过此方式,此处不进行总结