8.VUE-组件通讯

418 阅读3分钟

组件通讯 组件通信一般分为以下几种情况:

  • 父子组件通信 【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
  • 场景
  1. 需要对共享数据和行为进行拆分;
  2. 复杂的异步逻辑,需要综合多个模块进行状态演进,有详细的调试信息;
  3. 需要用到第三方插件;
  4. 由于由全局唯一数据源,方便进行跨平台,SSR中类似NUXT采用Vuex进行前后端数据同步。
  5. 需要综合考虑多个组件生命周期,先后顺序,实现特定逻辑。

★ 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没执行的原因

  • 原因:根据生命周期可以看的出来:on先执行,在执行on先执行,在执行emit触发事件 (嵌套关系)在生命周期中子组件没有加载on,就执行on,就执行emit方法的话是不会执行的.

  • 解决办法: 把父组件中的$emit事件放在mounted钩子函数中 等待子组件创建并注册on事件后再去触发on事件后再去触发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的实现原理

$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',//...)

原理

  1. 转化sat语法树,生成虚拟结点,虚拟结点上会有个属性叫listeners【listeners=data.on】

  2. 组件初始化的时候,把listeners放到_parentListeners属性上,_parentListeners定义在当前的vm.$options

  3. 初始化事件initEvent,执行updataComponentListeners(vm,vm.options._parentListeners)vm上有_eventsvm.options.\_parentListeners), vm上有\_events,vm.options上有个_parentListeners属性,指向的就是用户自己绑定的事

  4. updataComponentListeners方法里面调updataListeners,里面有add就是on,remove就是on ,remove就是delete,作用是把事件使用add还是remove添加到当前组件上

  5. add 本身就是onvon:myevent="doSomething",这样写会被转换成on v-on:my-event="doSomething",这样写会被转换成on 绑定事件,事件名叫my-event

  6. $ on 会把事件名和函数做一个映射关系,一对多的关系,一个函数名字可以对应多个函数,吧名字和函数对应到_events上,发布订阅

  7. 【v-on:my-event="fn,fn1" @my-event="fn1" @my-event="fn2" 】 【vm._events['my_event']=[fn,fn1,fn2]】

  8. $emit触发 就是让对应的函数去执行

  9. emit会通过事件名找到对应的回调函数,并且让回调函数依次去执行

Vue中的$on $emit就是典型的发布订阅,相当于组件把自定义的方法或事件定义到自己身上,在内部可以通过自己实例去触发这些方法


★ LocalStorage & Cookie & indexedDB


★ 什么时候使用vuex什么时候使用eventbus?

vuex

 vuex是专门为vue.js开发的状态管理模式

1.多个视图依赖于同一状态。 2.来自不同视图的行为需要变更同一状态。

它解决的多个组件依赖同一个状态的情况,适用于在单页面中,跨组件状态管理,登录状态管理,购物车管理等等

相对而言结构比较复杂,对于简单页面没必要使用 

  1. vuex是官方推出的,事件总线是高手在民间
  2. 在大型应用方面,vuex确实是一个比EventBus更好的解决方案
  3. vuex更加易于调试与管理

 

eventBus

eventBus又称事件总线,他使用的事订阅发布模式,一般于vue的prototype上注册一个事件中心,用于接收事件已经发送事件,以便全局使用

但是他又有一个比较严重的问题,维护困难,因为他没有明确可维护的列表,完全是定义了什么,才能用什么,一旦不是原开发者维护(甚至原开发者)会一团乱麻

emitemit和on导致了以下几个主要问题

1. 代码逻辑性极具下降,可阅读性变低
2. 对于每一个action父组件都需要一个on(或dispatch)一个事件来处理
3. 你将很难查找到每一个事件是从哪里触发,满篇都是业务逻辑

★ a链接跳转和history.push、vuex 和 eventbus数据变化情况

history.push vuex数据还在


参考文档: onon和emit的实现原理 EventBus & Vuex? vuex与eventbus vue状态管理机制探究(eventBus vs VUEX) vuex要在什么时候使用呢