浅谈 VueX 状态管理模式

前言

我们知道Vue组件通讯基本的方式有 子传父,父传子,平行兄弟组件通信,那么在简单的应用当中,Vue Store这种单一状态管理基本的方式就可以满足我们的日常需求,但是若要遇到大型复杂系统,你比如说,我有个数据,需要在多个实例,多层实例之间来回传递共享,那么一层一层的接收与发送就显得非常繁琐,并且组件之间的状态管理会显得十分麻烦,那么这个时候我们就应该考虑应用VueX了。

什么是VueX

VueXvue状态管理开发模式,以可预测的方式发生变化,那么首先先解释下相关基本组成成分

1.事件组成

  • dispatch : 触发事件,是唯一能执行action的方法
  • commit :    状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutate :    状态改变过程
  • render :    编译过程

2.基本对象组成

  • state : 存储状态(变量)
  • getters : 可以理解为store 的计算属性,说的通俗一点就是我可以通过getters将state重新赋值,重新定义
  • mutations : 修改状态并且是同步的方法(唯一改变state的途径)
  • actions : 提交的是 mutation,而不是直接变更状态。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发,该模块提供了Promise的封装,以支持action的链式触发
  • modules : store的子模块,为了开发大型项目,方便模块化状态管理而使用的

*注:VueX不能直接更改状态,必须通过提交(commit) mutations

3.基本流程

1.Vue组件接收状态并且交互,调用dispatch()方法
2.dispatch()方法触发actions里的相关对应方法
3.actions里的相关对应方法如果需要改变state状态,则调用commit()方法
4.commit()方法提交mutation修改state
5.render的过程通过getters对应的计算属性获取到新的state状态
6.重新渲染Components,更新视图

*注:VueX一旦更改了state状态,与之相关的所有组件都会收到相应变化

4.接下来我将写一个基于模块化的Vuex状态管理的使用(这里就是使用ModuleStore分割),因为基于单一的状态管理,所有的状态操作都存放在一个对象里,会变得十分繁琐,同样对象会堆得越来越大,那么首先我们先看下基本的结构是什么样子

const store = new Vuex.Store({
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... },
  modules: {
    a: moduleA,
    b: moduleB
  }
})

5.首先,我通过一个Demo实例来解释VueX如何使用,结构如下

image.png

6.这里我做了全局和局部模块拆分的处理,全局store存放在global.js,模块化的以文件为单位,存放在modules文件夹下,最后通过index.js合并导出.同样我用两个子组件ChildAChildB,一个父组件Parents来说明组件之间如何实现全局控制。代码示例如下

  • global.js
/**
 * Created by Echonessy on 2019.09.29
 * 全局store 配置
 */

//components 执行dispatch('addAction')
// 触发actions的addAction方法
//  addAction通过commit('add')触发mutations的add方法改变state
//  getter计算后重新渲染到页面上

//golbal state
export const state = {
  golbalNumber:0
}
//golbal getters
export const getters = {
  golbalNumber: state => state.golbalNumber+'_golbalGetters',
}
//golbal mutations
export const mutations = {
  addGolbalMutation:state => {state.golbalNumber++;},
  subGolbalMutation:state => {state.golbalNumber--;},
}
//golbal actions
export const actions = {
  addGolbalAction ({ commit }) {
    commit('addGolbalMutation')
  },
  subGolbalAction ({ commit }) {
    commit('subGolbalMutation')
  },
}

export default {
  state,
  getters,
  mutations,
  actions
}

  • childa.js
/**
 * Created by Echonessy on 2019.09.29
 * childa 模块 配置
 */
export const state = {
  number:1
}

export const getters = {
  childNumber: state => state.number+'_fromChildA',
}

export const mutations = {
  addMutation:state => {
    state.number++;
  },
  subMutation:state =>{
    state.number--;
  }
}

export const actions  = {
  addAction ({ commit }) {
    commit('addMutation')
  },
  subAction({ commit }){
    commit('subMutation')
  }
}

export default {
  namespaced: true, //在使用模块化的时候,dispatch('childb/addAction')
  state,
  getters,
  mutations,
  actions
}

  • childb.js
/**
 * Created by Echonessy on 2019.09.29
 * childb 模块 配置
 */
export const state = {
  number:1
}

export const getters = {
  childNumber: state => state.number+'_fromChildB',
}

export const mutations = {
  addMutation:state => {
    state.number++;
  },
  subMutation:state =>{
    state.number--;
  }
}

export const actions  = {
  addAction ({ commit }) {
    commit('addMutation')
  },
  subAction({ commit }){
    commit('subMutation')
  }
}

export default {
  namespaced: true, //在使用模块化的时候,dispatch('childb/addAction')
  state,
  getters,
  mutations,
  actions
}

  • index.js 主入口
import Vue from 'vue'
import Vuex from 'vuex'
import global from './global'

Vue.use(Vuex)

const modulesFiles = require.context('./modules', true, /\.js$/);
// module 合并
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1');
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default
  return modules
}, {})

const obj = {}
obj.modules = modules;
const options = Object.assign(global,obj)
const store = new Vuex.Store(options)

export default store

7.main.js主入口文件引入

import Vue from 'vue'
import App from './App'
import router from './router'
import store from "./store";

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
})

8.我们打印index.js里的options,结构如下,我们可以看到整个store的基本结构

image.png

9.上面已经讲了VueX的运行流程,那么我们在组件内如何去修改state? 我用一个Parents组件就能够完全说明

  • parents.vue
<template>
  <div>
    <div class="child">
      <child-a/>
      <child-b/>
    </div>
    <h2>我是父组件</h2>
    <div class="single">全局state {{$store.state}}</div>
    <div class="single">全局getters {{$store.getters}}</div>
    <div class="single">模块A => {{$store.getters['childa/childNumber']}}</div>
    <div class="single">
      <button @click="addGolbalEvt">全局修改加+</button>
      <button @click="subGolbalEvt">全局修改减-</button>
    </div>
    <div class="single">
      <button @click="addChildaEvt">全局修改模块A加+</button>
      <button @click="subChildaEvt">全局修改模块A减-</button>
    </div>
  </div>
</template>

<script>
export default {
  components: {
    childA: () => import('@/components/childA'),
    childB: () => import('@/components/childB'),
  },
  data(){
      return{}
  },
  methods:{
      addGolbalEvt(){
          this.$store.dispatch('addGolbalAction')
      },
      subGolbalEvt(){
        this.$store.dispatch('subGolbalAction')
      },
      addChildaEvt(){
          this.$store.dispatch('childa/addAction')
      },
      subChildaEvt(){
          this.$store.dispatch('childa/subAction')
      }

  }
}
</script>
<style>
  .child{border: 1px solid #ccc;box-sizing: border-box;padding: 30px;}
  .single{line-height: 40px;}
</style>

10.基本效果


11.最后代码地址:demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271