组件通讯 组件通信一般分为以下几种情况:
- 父子组件通信 【props emit】
- 兄弟组件通信
- 跨多层级组件通信 【provide / inject】
- 任意组件 【Vuex 、 EventBus、Vue实例、LocalStorage&Cookie&indexedDB】
★ props emit 单向数据流-【父子通信】
父组件通过 props 传递数据给子组件,子组件通过 emit 发送事件传递数据给父组件,这种父子通信方式也就是典型的单向数据流
★ 兄弟组件通信 【通过父组件】
对于这种情况可以通过查找父组件中的子组件实现,也就是 this.$parent.$children,在 $children 中可以通过组件 name 查询到需要的组件实例,然后进行通信。
★ provide / inject 【Vue 2.2API - 跨多层次组件通信】
Provider/Inject响应式需要组件提供,其他编译条件下可能获取不到this。
假设有父组件 A,然后有一个跨多层级的子组件 B
// 父组件 A
export default {
provide: {
data: 1
}
}
// 子组件 B
export default {
inject: ['data'],
mounted() {
// 无论跨几层都能获得父组件的 data 属性
console.log(this.data) // => 1
}
}
Vuex 、 Event Bus、Vue实例、LocalStorage & Cookie & indexedDB、 provide / inject
这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况
★ Vuex
vuex并不能让跨组件变得更简单和方便,反而会让他变复杂。
官网都说,如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。
原理: 全局注入store对象,来实现组件间的状态共享
运用到了js设计模式中的单例模式
理解
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态, store.state里面定义一些变量 store.mutations定义一些方法改变state的值 改变值的时候通过 store.commit('setCurChatId', item.chat_id);调用mutations里面的方法 使用的时候直接 store.state.curChatId 调用,
解决的主要问题是:
将事件源映射到状态变化这个过程从视图组件中剥离出来, 组织好这一部分的代码,在组件外部进行状态的管理,具体表现就是一个全局的数据中心 store 配置,每个组件进行更新的时候就通知数据中心,数据中心改变后,再去触发每个调用它的组件进行更新(这种更新是响应式的);几个核心概念就是 mutations 里的方法可以直接 mutate store 中的状态,并且mutation过程必须同步的,需要通过commit去触发;而actions则允许异步的操作,通过commit去触发mutation,达到间接修改 store 的目的,action本身需要通过 disptch去触发。
用法
//定义
state: {
curUserId: '',
},
mutations: {
setCurUserId(state, curValue) {
state.curUserId = curValue;
},
},
//调用
store.commit('setCurUserId', item.user_id);
//使用
store.state.curUserId
- 场景
- 需要对共享数据和行为进行拆分;
- 复杂的异步逻辑,需要综合多个模块进行状态演进,有详细的调试信息;
- 需要用到第三方插件;
- 由于由全局唯一数据源,方便进行跨平台,SSR中类似NUXT采用Vuex进行前后端数据同步。
- 需要综合考虑多个组件生命周期,先后顺序,实现特定逻辑。
★ Vue实例
你可以声明一个Vue实例,命名为store,然后利用其data和methods管理数据。
export const store = new Vue({
data: { count: 0 },
methods: {
add() {
this.count++;
}
}
});
然后在需要的组件内,通过computed获取数据,直接调用methods更新数据:
import { store } from "../App";
export default {
computed: {
count() {
return store.count;
}
},
methods:{
add(){store.add()}
}
}
★ Event Bus【任意组件 】
原理 【原理就是全局的vue实例】
它的工作原理是发布/订阅方法, 创建全 全局的vue实例,使用vue实现好的$emit和$on
const EventBus = new Vue()
EventBus.$emit('change')
EventBus.$on('change',//...)
// 跟父子组件传值,自定义事件的原理是一样的,用的是一个,只不过是因为实例不同而已
概念
EventBus 又称为事件总线,EventBus像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。
注意:eventbus需要取消订阅,频繁调用容易内存泄漏。
坑1:$emit和$on没执行的原因
-
原因:根据生命周期可以看的出来:emit触发事件 (嵌套关系)在生命周期中子组件没有加载emit方法的话是不会执行的.
-
解决办法:
把父组件中的$emit事件放在mounted钩子函数中等待子组件创建并注册emit。
坑2:为什么需要$off
-
造成原因:事件订阅是通过
$eventBus对象完成的,与组件无关,当你点击销毁后 再点击创建又会多了一个订阅事件,依次类推每次点击新建后都会多一个订阅事件 -
后果:如果不移除事件监听 并且会造成
内存泄漏 -
解决方案: 在子组件销毁后进行
取消订阅事件 this.$bus.$off('bus')
用法
假设你有两个Vue页面需要通信: A 和 B ,A页面 在按钮上面绑定了点击事件,发送一则消息,想通知 B页面。
const $eb = new Vue()
$eb.$emit('change')
$eb.$on('change',//...)
<!-- A.vue -->
<template>
<button @click="sendMsg()">-</button>
</template>
<script>
import { EventBus } from "../event-bus.js";
export default {
methods: {
sendMsg() {
EventBus.$emit("aMsg", '来自A页面的消息');
}
}
};
</script>
接下来,我们需要在 B页面 中接收这则消息。
<!-- IncrementCount.vue -->
<template>
<p>{{msg}}</p>
</template>
<script>
import {
EventBus
} from "../event-bus.js";
export default {
data(){
return {
msg: ''
}
},
mounted() {
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
}
};
</script>
同理我们也可以在 B页面 向 A页面 发送消息。这里主要用到的两个方法:
// 发送消息
EventBus.$emit(channel: string, callback(payload1,…))
// 监听接收消息
EventBus.$on(channel: string, callback(payload1,…))
★ $on和$emit的实现原理
应用
- 组件绑定事件,自定义事件
//父组件
<my-component v-on:my-event="doSomething"></my-component>
// 子组件
this.$emit('myEvent')
- EventBus
const $eb = new Vue()
$eb.$emit('change')
$eb.$on('change',//...)
原理
转化sat语法树,生成虚拟结点,虚拟结点上会有个属性叫listeners【listeners=data.on】
组件初始化的时候,把listeners放到_parentListeners属性上,_parentListeners定义在当前的vm.$options
初始化事件initEvent,执行updataComponentListeners(vm,vm.options上有个_parentListeners属性,指向的就是用户自己绑定的事
updataComponentListeners方法里面调updataListeners,里面有add就是delete,作用是把事件使用add还是remove添加到当前组件上
add 本身就是on 绑定事件,事件名叫my-event
$ on 会把事件名和函数做一个映射关系,一对多的关系,一个函数名字可以对应多个函数,吧名字和函数对应到_events上,发布订阅
【v-on:my-event="fn,fn1" @my-event="fn1" @my-event="fn2" 】 【vm._events['my_event']=[fn,fn1,fn2]】
$emit触发 就是让对应的函数去执行
emit会通过事件名找到对应的回调函数,并且让回调函数依次去执行
Vue中的$on $emit就是典型的发布订阅,相当于组件把自定义的方法或事件定义到自己身上,在内部可以通过自己实例去触发这些方法
★ LocalStorage & Cookie & indexedDB
★ 什么时候使用vuex什么时候使用eventbus?
vuex
vuex是专门为vue.js开发的状态管理模式
1.多个视图依赖于同一状态。 2.来自不同视图的行为需要变更同一状态。
它解决的多个组件依赖同一个状态的情况,适用于在单页面中,跨组件状态管理,登录状态管理,购物车管理等等
相对而言结构比较复杂,对于简单页面没必要使用
- vuex是官方推出的,事件总线是高手在民间
- 在大型应用方面,vuex确实是一个比EventBus更好的解决方案
- vuex更加易于调试与管理
eventBus
eventBus又称事件总线,他使用的事订阅发布模式,一般于vue的prototype上注册一个事件中心,用于接收事件已经发送事件,以便全局使用
但是他又有一个比较严重的问题,维护困难,因为他没有明确可维护的列表,完全是定义了什么,才能用什么,一旦不是原开发者维护(甚至原开发者)会一团乱麻
on导致了以下几个主要问题
1. 代码逻辑性极具下降,可阅读性变低
2. 对于每一个action父组件都需要一个on(或dispatch)一个事件来处理
3. 你将很难查找到每一个事件是从哪里触发,满篇都是业务逻辑
★ a链接跳转和history.push、vuex 和 eventbus数据变化情况
history.push vuex数据还在
参考文档: emit的实现原理 EventBus & Vuex? vuex与eventbus vue状态管理机制探究(eventBus vs VUEX) vuex要在什么时候使用呢