探究Vuex的使用和原理


在使用Vue框架的时候,经常会面临着组件间传值的问题,这时候就需要使用Vuex作为其状态管理器。

简单回顾一下,传统的Vue组件常用的通信的方式有:

  • 父子通信: 父→子传值(props),子→父传值(events($emit));父调用子方法ref;provide/indect
  • 兄弟通信: eventBus中央事件总线,担任组件之间通信桥梁
  • 跨级嵌套通信: bus; provide/indect(Vue2.2 新增api),bus机制同上,子组件绑定方法,触发时发布eventBus,每个子组件做订阅监控,触发在created里面的方法执行;provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
    (关于详细的常用组件通信方式介绍,之后会✏️写文章专门介绍~)

在大型的复杂项目中,可能会存在着多级组件的嵌套,需要实现一个组件更改某个数据,多个组件便可以自动获取更改后的数据进行业务逻辑处理,这时就轮到今天的主角Vuex闪亮出场了。

一、Vuex是什么?

Vuex的介绍文档

Vuex就是一个专门为Vue.js应用程序开发的状态管理模式,类似于React中的Redux。

Vuex的组成部分:

  • state, 驱动应用的数据源
  • view, 以声明的方式将state映射到视图
  • actions,响应在view上的用户输入导致的状态变化

“单向数据流”示意当多个组件共享状态时,上述的单向数据流的简易性就会很容易被破坏:1)多个视图依赖于同一状态,来自不同视图的行为需要变更同一状态

Vuex的设计思想:
借鉴了Flux、Redux,将数据存放到全局的store,再将store挂载到每个vue实例组件中,利用Vue.js的细粒度数据响应机制进行高效的状态更新

二、Vuex的使用

Vuex的使用

import Vuex from 'vuex';
Vue.use(Vuex); // 1.Vue的插件机制,安装Vuex
let store = new Vuex.Store({ // 2.实例化store,调用install方法
  state:{
    count: 1
  },
  getters,
  modules,
  mutations,
  actions,
  plugins
});
new Vue({ // 3.注入store,挂载Vue实例
  store,
  render: h => h(app)
}).$mount('#app')

Vuex只是一个状态管理器,存储的数据时响应式的,并不会保存,刷新之后就回到了初始的状态,如果想保存改变的数据,最好结合LocalStorage(需要注意,Vuex的数据都是以数组形式保存,而Localstorage只支持字符串,所以记得使用JSON转换:Array → String : JSON.stringify() ;String → Array: JSON.parse()

let defaultCity = "上海"
try{ // 用户关闭了本地存储,在外层加一个try...catch
  if(!defaultCity){
    defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
  }
}catch(e){}

export default new Vuex.Store({
  state:{
    city: defaultCity
  },
  mutations:{
    changeCity(state, city){
      state.city = city
      try{
        window.localStorage.setItem('defaultCity', JSON.stringify(state.city))
        // 数据改变的时候拷贝一份到LocalStorage中
      }catch(e){}
    }
  }
})

三、Vuex的原理

Vuex的实现原理1.Vuex的原理
Vuex实现了一个单向的数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action,但 Action 也是无法直接修改 State 的,还是需要通过 Mutation 来修改 State 的数据。最后,根据 State 的变化,渲染到视图上。

2.Vuex中各个模块的详细介绍

  • State:页面状态管理容器 集中存储 Vue 组件中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
    • getter(): store的计算属性,state对象读取方法,包含在render中。Vue组件通过它读取全局state对象,getter返回的值会根据他的依赖被缓存起来,且只有当他的依赖值发生改变时才会被重新计算。
  • Actions: 操作行为处理模块 组件中的$store.dispatch('action 名称',data)来触发,然后由commit()来触发mutation的调用,间接更新state(因此actions更像是一个装饰器,提交mutation但不直接变更state)。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
    • dispatch(): 操作行为触发方法,唯一能执行action的方法
  • Mutations: 状态改变操作方法actions中的commit('mutation 名称')来触发。Vuex 修改 state 的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
    • commit(): 状态改变提交方法,唯一能执行mutation的方法 ,store.commit()更改state存储的状态
  • Module:store分割的模块:将store分成模块(modules)每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割,从而提升代码的可维护性

🔸 action 类似于 mutation,区别在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作,而Mutation只能且必须是同步操作。

3.Vuex中两个重要的问题

  • ❓ store如何挂载注入到组件中?
  • ❓ stategetters如何映射到各个组件实例中响应式更新状态?

Q1:store挂载注入组件

**A:**vuex是利用vue的mixin混入机制,在beforeCreate钩子前混入vuexInit方法,vuexInit方法实现了store注入vue组件实例,并注册了vuex store的引用属性$store。
store注入组件的过程
Q2:state和getters如何映射到组件实例中响应式更新状态

A: 通过分析源码中的resetStoreVM核心方法,可以发现:

  • Vuex的state状态是响应式,是借助Vue的data是响应式,将state存入vue实例组件的data中
  • Vuex的getters则是借助Vue的计算属性computed来实现数据实时监听

说了半天,Vuex还是比较适合逻辑偏复杂的应用,不然说实在难免会有些小题大做~

四、Vuex的高级使用

场景: 点击某个地方选择城市,所有涉及到城市的地方都变化

安装vuex: npm install vuex --save

新建src/store/index.js,并在其中引用:
import Vue from 'vue'
import Vuex from 'vuex'

基本使用示例:

  1. 某组件中触发状态改变的函数
handleClick = function(city){
  this.$store.commit('changeCity', city)
}
  1. 在store/index.js下面设置状态的改变
    为了易于管理和维护,分离开state,mutations

    ① store/index.js:

    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import mutations from './mutations'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      mutations
    })
    

    ② store/state.js:

    let defaultCity = '上海'
    try {
      if (localStorage.city) {
        defaultCity = localStorage.city
      }
    } catch (e) {}
    
    export default {
      city: defaultCity
    }
    

    ③ store/mutations.js:

    export default {
      changeCity (state, city) {
        state.city = city
        try {
          localStorage.city = city
        } catch (e) {}
      }
    }
    
    
  2. 在组件中调用 this.$store.state.city

高级使用示例:

vuex还为我们提供了非常高级的api——mapState、mapMutations、mapActions

mapState——把vuex中的数据映射到组件的computed的计算属性中

import { mapState } from 'vuex'
export default {
  computed: {
    ...mapState(['city'])
  }
}

建立了这样的映射之后,组件中使用数据时的this.$store.state.city就可以替换为this.city

除了数组的映射,还可以实现对象的映射,eg:

computed: {
  ...mapState({
    currentCity: 'city'
  })
}

调用只需要this.currentCity

此外,还有简单的方法mapMutations,用法同mapState
import { mapState, mapMutations } from 'vuex'

methods: {
  handleCityClick (city) {
    // dispatch派发一个名字为changeCity的action,配合vuex中的actions
    // this.$store.commit('changeCity', city)
    this.changeCity(city) // 使用mapMutations之后可以这样调用
    this.$router.push('/')
  },
  ...mapMutations(['changeCity'])
},

对于功能复杂的应用,可以使用Mudules对代码进行拆分,实现vuex的高级使用

作为状态管理工具,Vuex采用集中式储存管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变换

📝Vuex并没有限制代码结构,但是,规定了一些需要遵守的规则:

1)应用层级的状态应该集中到单个的store对象中
2)提交 mutation是更改状态的唯一方法,并且这个过程是同步的
3)异步逻辑都应该封装到action里面


Author: Casey Lu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Casey Lu !
评论
 Previous
浅谈Node的两三事 浅谈Node的两三事
大多数情况下,我们所见的Javascript是运行在浏览器中的,而事实上,JS是一门"完整"的语言,浏览器只是提供给了它一个上下文。Node.js实际上也是一种上下文,它允许在后端运行JS代码。 针对Node的总结,cc
2020-03-12
Next 
Javascript中的闭包Closure Javascript中的闭包Closure
对JS中的闭包还没有了解过的可看 MDN 闭包, 在面试的时候闭包也是经常被提及的Topic 一、闭包的概念 简单的说,闭包就是有权访问其他函数作用域内部变量的函数 闭包 = 函数 + 函数能够访问的自由变量 闭包的三个特点: 函数嵌套函
2020-03-10
  TOC