微信号:QunarTL

介绍:Qunar技术沙龙是去哪儿网工程师小伙伴以及业界小伙伴们的学习交流平台.我们会分享Qunar和业界最前沿的热门技术趋势和话题;为中高端技术同学提供一个自由的技术交流和学习分享平台.

深入Vuex 原理(下)

2019-01-23 09:30 孔维国


点击箭头处“蓝色字”,关注我们哦!!

孔维国,2016年加入去哪儿网技术团队。目前在大住宿事业部/增值业务研发中心,参与开发了TMC、CRM、QTA、Auth等项目,负责node框架nomi的设计以及开发。乐于探索布道新技术,在大前端的道路上越走越偏!在node领域上越陷越深!


前言

在深入Vuex原理(上)中,我们回答了 “Vuex 的 Store 是如何注入到组件中的?” 的问题,下面我们继续对以下问题进行探讨。 Vuex 的 state 和 getter 是如何映射到各个组件实例中自动更新的? 首先,我们对问题进行简单剖析。


注:本篇文章只展示关键核心代码,一来由于篇幅原因,二来展示核心代码更容易让人理解。再者,本篇属于 Vuex 高级篇,对于本篇章中涉及的 Vue 相关的机制以及 Vuex 的高级使用等不进行过多赘述,请自行前往官网查看。


准备

疑问:Vuex 的state 和 getter 是如何映射到各个组件实例中自动更新的呢?

问题剖析

该问题的核心问题是当 Store 中的 state 和 getter 方式变更时,Vuex 如何保证各个组件实例中的数据自动更新,并 update 组件?简言之,某一组件 Store 更新时,如何通知其他组件进行数据更新,和 UI 更新。通过简单分析可知,问题的根本就是组件通信的问题。

浅谈组件通信

从分析可知,要解答本篇疑问,我们需要从 Vue 组件通信谈起。在使用 Vue 的过程中,需要频繁的进行组件间通信,通信的主体之间的关系可以是父子组件,也可以是类似兄弟组件或者是无关组件等非父子组件。总的来说有如下几种方式: 1.通过 props 向子组件传递数据:父->子; 2.通过事件向父组件发送消息:子->父,使用 $emit 发送事件; 3.父链和子索引:this.$parent 与 this.$children; 4.依赖注入:provide 和 inject; 5.子组件引用:ref与$refs; 6.特性绑定:v-bind="$attrs" 和 v-on="$listeners"; 7.vent bus; 8.$dispatch 和 $broadcast:在 Vue1 中使用; 9.利用全局变量 storage、cookie、query、hash 等传递数据: 非 Vue 特性,不做赘述; 10.全局事件广播。 下图展示了上述方案1~6的组件通信方式。

由于方案8官方已经不建议在 Vue2 中使用,此处不做赘述;而方案9~10不属于 Vue 特性,而是前端通用的数据通信方案,此处也不进行赘述,如有其它方案,欢迎补充。 下面,我们主要介绍一下与本文内容息息相关的方案7,中央事件总线的解决方案。其核心设计思想是引入中央通信桥梁——中央事件总线,使各个组件只与其进行通信,达到数据同步的通信目的。如下图 组件 A 数据变更,通知中央事件总线,其他组件监听并接收变更的数据。

下面代码展示了在 Vue 中使用中央事件总线进行组件通信:

 
           
  1. let bus = new Vue({

  2.     methods: {

  3.         emit (event, ...args) {

  4.             this.$emit(event, ...args);

  5.         },

  6.         on (event, callback) {

  7.             this.$on(event, callback);

  8.         }

  9.     }

  10. });


  11. //component A

  12. bus.emit('updateData', data); // 发送数据给 bus。


  13. //component B

  14. bus.on('updateData', data => {

  15.     // 接收 updateData事件 发送的数据信息。

  16. })

Vue 的中央事件总线的实现简单讲就是新建了一个 Vue 对象,借助 Vue 对象的特性($emit 与 $on)作为其他组件的通信桥梁,实现组件间的通信以及数据共享。

探秘原理

本部分将针对以上疑问,通过源码分析,剖析核心代码,对问题进行解答。

使用

我们探讨的是 state 和 getter,首先我们先来看一下在 Vue 组件中如何方便的获取 Vuex 的 state 和 getter 吧。

 
           
  1. this.$store.state.xxx;

  2. this.$store.state.moduleA.xxx;


  3. this.$store.getters.xxx;

  4. this.$store.getters.moduleA.xxx;


正如我们所知的,Vuex 的 Store 会划分出 state 和 getters 两个数据区。getter 是从 store 的 state 中派生出的状态。代码如下:

 
           
  1. // 初始化store时,划分出 state数据区 与 getters数据区

  2. new Vuex.Store({state, getters});

源码分析

首先,我们先来看 state。从源码中我们找到了 state 的 get 方法,如下:

 
           
  1. get state () {

  2.    return this._vm._data.$$state

  3.  }

从源码得知,在 Vue 组件中,使用 this.$store.getters.xxx 获取 xxx 属性时,实际上是获取的 store._vm.data.$$state 对象上的同名属性。那么我们将关注点放在 _vm 上。我们通过 Store 的 constructor 找到了处理 state 和 getter 的核心函数 resetStoreVM(this, state)。其核心代码如下:

 
           
  1. store._vm = new Vue({

  2.    data: {

  3.      $$state: state

  4.    }

  5.  })

上述代码初始化了一个 Vue 实例 _vm,由于 Vue 的 data 是响应式的,所以,$$state 也是响应式的,那么当我们在一个组件实例中对 state.xxx 进行 更新时,基于 Vue 的 data 的响应式机制,所有相关组件的 state.xxx 的值都会自动更新,UI 自然也会自动更新!可见,这和 Vue 的中央事件总线 设计思想如出一辙,同样借助 Vue 对象特性(响应式的data)作为其他组件的通信桥梁,实现组件间的通信 以及数据共享。

总结

Vuex 的 state 是借助 Vue 的响应式 data 实现的。设计思想与 Vue 中央事件总线如出一辙。

猜测

Vue 中 computed 从 data 中派生出的计算属性,Vuex 中 getter 是从 state 中派生出的属性;而 Vuex 中的 state 是借助 data 实现的,那么 getter 是否是借助 computed 实现的呢?

源码验证

在 resetStoreVM 中可以看到对于 wrappedGetters(经过包装,收集的 getters,处理细节忽略)的处理。

 
           
  1. const computed = {};

  2. // 处理getters

  3. forEachValue(wrappedGetters, (fn, key) => {

  4.    computed[key] = () => fn(store) // 将getter存储在computed上

  5.    //this.$store.getters.XXX的时候获取的是store._vm.XXX

  6.    Object.defineProperty(store.getters, key, {

  7.      get: () => store._vm[key],

  8.      enumerable: true

  9.    })

  10. })

上述代码,对 wrappedGetters 进行处理,让 getter 存储至 computed 对象上,下面我们关注后续 computed 的处理即可。

 
           
  1. store._vm = new Vue({

  2.    computed

  3.  })

上述代码将 computed 对象挂载至 Vue 实例 _vm 的 computed 属性上,得益于 Vue 的计算属性特性,数据的变更同样可以同步至其他相关组件上。这与我们的猜测一致。getter 的实现借助了 Vue 的 computed 的特性而实现。 分析至此,我们已经得出该问题的答案。

结论

1.Vuex 的 state 是借助 Vue 的响应式 data 实现的。 2.getter 是借助 Vue 的计算属性 computed 特性实现的。 3.其设计思想与 Vue 中央事件总线如出一辙。 更多 Vuex 原理探究,请前往深入Vuex原理(上)。

 
Qunar技术沙龙 更多文章 AIOps 助力航信运维百万级交易系统 0 宕机 机器学习之scikit-learn开发入门(6) 机器学习之scikit-learn开发入门(7) 机器学习之scikit-learn开发入门(8) 2018 Qunar 心中有“数”
猜您喜欢 Go语言并发模型:使用 select Python 3.7 中的 PEP 562【6.28 热门分享回顾】 #include #import @class 讲解 快走!《华清logo变迁史》观众席开抢了! 手慢无|StuQ 邀你免费参加《深入Vue 2.0 实战》付费小班课首节预演