使用Vuex

Vuex用来在Vue中实现单向数据流,适用于状态管理极多,页面数据结构非常复杂的大型前端应用(其实一般XX管理系统都属于这个范畴)。React中我们经常使用Redux实现类似功能,Vuex和Redux略有不同,相比Redux进行了一些改进,使用更加简单优雅,而且容易理解,如果会Redux就能立刻上手使用Vuex。

这里我们参考官方文档学习一下Vuex。

https://vuex.vuejs.org/zh/

安装

vue-cli3下,我们直接用命令安装vuex

vue add vuex

安装完成后,vue-cli会自动为我们创建一个store.js,并在入口文件中注册到Vue实例,我们可以在其基础上测试。

Store和State

Vue是数据驱动页面的,Vuex实现了单向数据流,组件数据统一放在Vuex的Store中保存,存储数据的对象我们叫State。下面是一个计数器的例子,每点一次按钮计数值加1。

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    cnt: 0
  },
  mutations: {
    inc: function(state) {
      state.cnt++;
    }
  }
})

上面代码中就是实例化的一个Store,其中state.cnt就是我们保存的数据。Vuex中,通过提交mutation的方式对数据进行同步修改。

Cnt.vue

<template>
    <div>
        <div v-text="cnt"></div>
        <button @click="inc">+</button>
    </div>
</template>
<script>
export default {
    name: 'Cnt',
    computed: {
        cnt: function() {
            return this.$store.state.cnt;
        }
    },
    methods: {
        inc: function() {
            this.$store.commit('inc');
        }
    }
}
</script>

注意读取和设置Store中数据的方法,我们通过this.$store访问Store对象,读取直接读就行了,我们读取Store数据并放在计算属性中,就可以在组件模板中访问了。

而设置State必须通过commit()提交mutation实现,参数就是mutation的函数名。

Getter

我们直接读this.$store.state,这个数据可能还有一些后续处理步骤,这些逻辑如果数据数据模型本身而不是渲染逻辑,都写在组件的computed属性中就不太好了。Store中也支持计算属性,只不过它叫Getter

export default new Vuex.Store({
  state: {
    cnt: 0
  },
  getters: {
    cntFormatted: function (state) {
      return '当前值为:' + state.cnt + "GB";
    }
  },
  mutations: {
    inc: function (state) {
      state.cnt++;
    }
  }
})

上面例子代码中,我们在getters中编写了一些类似组件「计算属性」的逻辑。

computed: {
        cntFormatted: function() {
            return this.$store.getters.cntFormatted;
        }
    }

组件中,通过上述写法访问Store的Getter。

Mutation

之前我们简单使用了Mutation提交数据,这里再深入学习一下。

提交Mutation

定义Mutation:

mutations: {
  inc: function (state) {
    state.cnt++;
  }
}

在组件中提交Mutation:

this.$store.commit('inc');

携带载荷

提交Mutation时可以携带一些参数,这些参数通常封装在一个对象中,我们称为payload。假设现在我们实现填写一个客户信息表单,点击确认按钮将客户数据提交到Store中,commit这个动作就需要携带数据了。

store.js中Mutation定义:

mutations: {
  addCust: function (state, payload) {
    state.custs.push(payload);
  }
}

提交写法:

methods: {
  handleClick: function() {
    let payload = {
      custId: this.custId,
      custName: this.custName,
      sex: this.sex,
      age: this.age
    };
    this.$store.commit("addCust", payload);
  }
}

其实,我这里将数据用v-model绑定到了组件的data上,然后组装为payload,通过commit提交Mutation。你也可以在handleClick中,读取一次表单值。

Action

Mutation只能进行同步处理,而不能包含异步处理,这个很好理解。如果希望使用异步处理,自然也就需要在Mutation上封装一层。带有异步操作的数据更新需要使用Action,而Action必须通过提交Mutation改变数据。

在上面的基础上,假设我们要在组件初始化时,从后台加载已有的客户数据到Store中,就需要在组件的created()回调函数里提交一个Action。先看StoreAction的定义:

actions: {
  loadTable: function ({ commit }, queryParam) {
    window.console.log(queryParam);
    // 模拟一个异步请求
    setTimeout(function () {
      let cust1 = {
        custId: 1,
        custName: 'tom',
        age: 3,
        sex: 'male'
      };
      let cust2 = {
        custId: 2,
        custName: 'jerry',
        age: 1,
        sex: 'female'
      };
      let cust3 = {
        custId: 3,
        custName: 'lucy',
        age: 9,
        sex: 'female'
      };
      commit("addCust", cust1);
      commit("addCust", cust2);
      commit("addCust", cust3);
    }, 3000);
  }
}

这个Action其实就是模拟几个客户数据,然后用setTimeout()模拟一个异步操作,假装是从后端载入的。数据加载后,我们通过commit()提交Mutation更改Store中数据。

每个Action中,第一个参数是context,我们一般通过解构赋值读取其中的几个参数:

{
  state,      // 当前模块的State(见后文State多模块)
  rootState,  // 根State
  commit,     // 用于提交Mutation
  dispatch,   // 用于提交其它Action
  getters,    // 访问Store的计算属性,相当于当前模块的store.getters
  rootGetters // 访问Store的计算属性,相当于根模块的store.getters
}

第二个参数是dispatch()payload

提交Action

this.$store.dispatch("loadTable", "123")

其中,loadTableAction的名字,123payload

Module

Vuex将整个项目的数据结构提取出来集中存储,但是我们的项目如果过于复杂,把所有数据结构维护在一起肯定是不行的,还得分模块把它们拆开。

假设我们把Store拆为子模块moduleAmoduleB,再加上根模块本身,一共三部分。子模块的写法和根模块一样,无非就是包含statemutationsactionsgetters的一个对象。

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

上面代码实现在根模块中注册子模块。

Vuex中和Store相关的项目目录结构

安装Vuex时,vue-cli自动为我们创建了一个store.js但是随着我们项目变得庞大,只用一个store.js是远远不够的,我们需要创建一个单独的store文件夹,存放相关的内容。例如:

store
|_index.js 初始化Store
|_actions.js 定义所有的根Action
|_mutations.js 定义所有的根Mutation
|_module1 模块1相关的文件
  |_index.js 模块1中Store的初始化
  |_actions.js 模块1中所有的Action
  |_mutations.js 模块1中所有的Mutation
|_...
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap