Vue 项目中组件通信一直是一个很头疼的问题,本文将会介绍多种组件通信方式。
一、props和$emit
1.1 父组件向子组件传输数据
经常使用 vue.js 的开发者对 props 想必不会陌生,props 和 $emit 的组合是日常运用非常多的一种父子组件间通信的方式。 props 十分符合单项数据流的概念,即数据只能由父组件流向子组件,而子组件不能通过修改父组件通过 props 传过来的值间接的修改父组件。
1.2 单向数据流
为什么这样做,Vue官网中给出的解释是:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
1.3 子组件通知父组件
单向数据流限制了数据流只能从父组件传递到子组件,那么子组件是否无法影响到父组件呢?这时候就是 $emit 派上用场的时候了。$emit 可以触发绑定在当前这个实例上的事件。即我们可以在父组件中创建个处理状态变化的对应方法,并监听子组件的这个状态变化,在子组件触发这个状态变化时,调用对应的方法。
1.4 示例代码
结果显示:
代码:
// home.vue 父组件
<template>
<div class="home">
<h3>Home</h3><button @click="sendDate">prop传值</button>
{{date}}
<!-- 通过v-bind绑定在mdate上传递给子组件,date对应父组件实际传递的值 -->
<!-- 通过v-on绑定dateChanged状态,并确定状态修改的触发事件为 parentChange -->
<MTitle :mdate="date" @dateChanged="parentChange"/>
<MNews :mdate="date" @dateChanged="parentChange"/>
<MFooter :mdate="date" @dateChanged="parentChange"/>
</div>
</template>
<script>
// @ is an alias to /src
// import MTitle from '@/components/MTitle.vue'
const MTitle = () => import('@/components/MTitle.vue')
const MNews = () => import('@/components/MNews.vue')
const MFooter = () => import('@/components/MFooter.vue')
export default {
name: 'home',
components: {
MTitle,
MNews,
MFooter
},
data () {
return {
date: '1000'
}
},
methods: {
sendDate: function () {
this.date = Date.parse(new Date()).toString()
console.log(this.date)
},
parentChange: function (val) {
this.date = val
}
}
}
</script>
// MTitle.vue 子组件
<template>
<div class="MNews">
MNews
<!-- 使用父组件传过来的值 -->
<div>{{mdate}}</div>
<button @click="changeDate">修改父组件</button>
</div>
</template>
<script>
export default {
name: 'MNews',
components: {},
// props 获取父组件传下来的参数
props: {
mdate: String
},
data () {
return {
mes: this.mdate // 子组件不能直接修改父组件的传值,需要赋值
}
},
methods: {
changeDate: function () {
this.mes = Date.parse(new Date()).toString()
this.$emit('dateChanged', this.mes) // 触发状态修改,并传参给父组件,让父组件作出修改动作
}
}
}
</script>
<style lang="less">
.MNews {
height: 100px;
background: green;
color:#fff;
}
</style>
二、事件总线:EventBus
2.1 什么是事件总线
这种方式通过一个创建一个空的 vue 实例 EventBus 作为事件总线,所有的组件都能自由地在这个 EventBus 上监听 ($on) 和修改 ($emit) 状态。从而实现了轻量化的跨组件间数据传递,这种方式可以很好处理父子、兄弟以及跨级组件之间的数据交互。
2.2 核心代码
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
2.3 示例代码
结果显示:
代码:
在main.ts中,新构建一个代表事件总线的空 vue 实例,并绑定到 vue 的属性中,以便所有组件都能访问
Vue.prototype.$EventBus = new Vue()
在组件A中:ComA.vue,触发修改姓名事件,去修改组件C的姓名。同时监听组件C可能触发的 reset 事件
<template>
<div class="">
组件A {{name}}
<button @click="changeName">修改C组件的Name</button>
</div>
</template>
<script>
export default {
name: 'ComA',
components: {},
data () {
return {
name: 'benny'
}
},
mounted () {
// 事件监听一般是在 create 或 mounted 中
this.$EventBus.$on('resetAll', ({ name, age }) => {
this.name = name
})
},
methods: {
changeName () {
// 发送修改姓名事件
this.name = 'benny'
this.$EventBus.$emit('changeName', this.name)
}
}
}
</script>
在组件B中:ComB.vue,触发修改年龄事件,去修改组件C的年龄。同时监听组件C可能触发的 reset 事件
<template>
<div class="">
组件B {{age}}
<button @click="changeAge">修改C组件的Age</button>
</div>
</template>
<script>
export default {
name: 'ComB',
components: {},
data () {
return {
age: 10
}
},
mounted () {
// 事件监听一般是在 create 或 mounted 中
this.$EventBus.$on('resetAll', ({ name, age }) => {
this.age = age
})
},
methods: {
changeAge () {
// 发送修改年龄事件
this.age = 10
this.$EventBus.$emit('changeAge', this.age)
}
}
}
</script>
在组件C中:ComC.vue,监听组件A发出的修改名称事件以及组件B发出的修改年龄事件,同时可能触发重置事件,重置姓名和年龄。
<template>
<div class="">
组件C {{name}} {{age}}
<button @click="resetAll">重置name和age</button>
</div>
</template>
<script>
export default {
name: 'ComC',
components: {},
data () {
return {
name: '',
age: 0
}
},
mounted () {
// 事件监听一般是在 create 或 mounted 中
this.$EventBus.$on('changeName', name => {
this.name = name
})
this.$EventBus.$on('changeAge', age => {
this.age = age
})
},
methods: {
resetAll: function () {
// 发送重置事件
this.name = 'tom'
this.age = 15
this.$EventBus.$emit('resetAll', { name: this.name, age: this.age })
}
}
}
</script>