Skip to content

Vuex 状态管理 ​

Vuex 是专为 Vue.js 应用程序开发的状态管理模式,核心概念包括 State、Mutation、Action、Getter 与 Module。

一、介绍 ​

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

核心概念:State、Mutation、Action、Getter、Module。

注意:刷新页面会导致 state 重置,可以通过 state 是否被重置来判断浏览器是否刷新;也可以结合 localStorage 实现永久存储。

二、安装 ​

bash
npm install vuex@^3.4.0 --save

实际安装的版本可以查看 yarn.lock(本次为 3.6.2)。

三、特点 ​

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态时,若 store 中的状态发生变化,相应的组件也会高效地得到更新。
  2. 不能直接改变 store 中的状态。改变 store 中状态的唯一途径是显式地提交 mutation。

四、创建模块文件 ​

创建 store 文件夹,并在其中创建各个模块文件。

1. index.js ​

js
import Vue from "vue";
import Vuex from "vuex";

import state from "./State.js";
import mutations from "./Mutations.js";
import actions from "./Actions.js";
import getters from "./Getters";

import themeColor from "./ThemeColor"; // module

Vue.use(Vuex);

const store = new Vuex.Store({
  state,
  mutations,
  actions,
  getters,
  modules: {
    themeColor,
  },
});

export default store;

2. State.js ​

js
const state = {
  count: 1,
  userName: "", // 用来判断页面是否刷新
  todoList: [
    {
      title: "TypeScript",
      status: false,
    },
    {
      title: "Nodejs",
      status: false,
    },
    {
      title: "Nginx",
      status: false,
    },
    {
      title: "uni-app",
      status: false,
    },
    {
      title: "跨域解决方案",
      status: true,
    },
    {
      title: "Vue性能提升",
      status: true,
    },
  ],
  imageUrl: "",
};

export default state;

3. Mutations.js ​

js
const mutations = {
  set_userName(state, payload) {
    state.userName = payload.data;
  },
  set_count(state, payload) {
    state.count = payload.data;
  },
  set_imageUrl(state, payload) {
    state.imageUrl = payload.data;
  },
  set_todoList(state, payload) {
    switch (payload.type) {
      case "add":
        state.todoList.unshift({
          title: payload.data,
          status: false,
        });
        break;
      case "edit":
        state.todoList.forEach((item, index) => {
          if (payload.index === index) {
            item.title = payload.data;
          }
        });
        break;
      case "delete":
        state.todoList.splice(payload.index, 1);
        break;
      default:
        break;
    }
  },
};

export default mutations;

4. Getters.js ​

实现 Vuex 数据持久化:先判断是否刷新了页面,如果 state 被重置就从 localStorage 中读取数据,否则直接取仓库中的值。

js
const getters = {
  /* Vuex 实现数据持久化 */
  user_name: (state) => {
    if (state.userName === "") {
      return localStorage.getItem("user_name");
    }
    return state.userName;
  },
};

export default getters;

5. Actions.js ​

js
const actions = {
  setCount({ commit }, payload) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit("set_count", payload);
        resolve();
      }, 1000);
    });
  },
};

export default actions;

也可以在 actions 中获取接口数据,并把数据存到 Vuex 中:

js
async GET_STARS({ commit }) {
  const { data } = await axios.get('http://my-api/stars')
  commit('SET_STARS', data)
},

6. Module ​

在 store 文件夹下新建 ThemeColor 文件夹,专门存放系统颜色,并在其中新建 index.js、state.js、mutations.js。

js
// index.js
import state from "./state";
import mutations from "./mutations";

export default {
  namespaced: true, // 模块命名空间
  state,
  mutations,
};
js
// state.js
const state = {
  echartColor: "#333",
  fleetBg: "rgb(6,42,88)",
};

export default state;
js
// mutations.js
const mutations = {
  set_echartColor(state, payload) {
    state.echartColor = payload.data;
  },
  set_fleetBg(state, payload) {
    state.fleetBg = payload.data;
  },
};

export default mutations;

7. 注册到全局 ​

js
// main.js
import store from "@/store/index.js";

new Vue({
  store, // 把 store 实例注入到所有的子组件中
  render: (h) => h(App),
}).$mount("#app");

五、辅助函数 ​

当一个组件需要获取多个状态时,可以借助 mapState 辅助函数。为了提高复用性,把这些辅助函数写在混入中。

1. 根 store 的映射 ​

js
// mixin.js
import { mapState, mapMutations, mapActions, mapGetters } from "vuex";

export const vuexRoot = {
  computed: {
    /* 把 this.count、this.userName 等映射为 this.$store.state.xxx */
    ...mapState(["count", "userName", "todoList", "imageUrl"]),
    /* 把 this.user_name 映射为 this.$store.getters.user_name */
    ...mapGetters([
      "user_name",
      // ...
    ]),
  },
  methods: {
    // 把 this.set_userName({ data: value }) 映射为
    // this.$store.commit('set_userName', { data: value })
    ...mapMutations(["set_userName", "set_imageUrl", "set_todoList"]),
    // 把 this.setCount(amount) 映射为
    // this.$store.dispatch('setCount', amount)
    ...mapActions(["setCount"]),
  },
};

2. 命名空间模块的映射 ​

js
export const vuexThemeColor = {
  computed: {
    // 把 this.echartColor 映射为
    // this.$store.state.themeColor.echartColor
    ...mapState("themeColor", ["echartColor", "fleetBg"]),
  },
  methods: {
    // 把 this.set_echartColor({ data: value }) 映射为
    // this.$store.commit('themeColor/set_echartColor', { data: value })
    ...mapMutations("themeColor", ["set_echartColor", "set_fleetBg"]),
  },
};

六、在组件中使用 ​

vue
<!-- a.vue:注册局部混入 -->
<script>
import { vuexRoot, vuexThemeColor } from "@/mixin";

export default {
  mixins: [vuexRoot, vuexThemeColor],
  mounted() {
    console.log(this.userName); // 取得 state 中的值
    console.log(this.user_name); // 取得 getter 中的值
    this.set_userName({ data: this.ruleForm.name }); // 调用 mutation 方法
    this.setCount({ data: 4 }).then(() => {
      // 调用 action 方法
      console.log(this.$store.state.count);
    });
  },
};
</script>

七、注意点 ​

1. Mutation 需遵守 Vue 的响应规则 ​

  • 最好提前在 store 中初始化好所有的属性。
  • 当需要在对象上添加新属性时,应该使用 Vue.set(obj, 'newProp', 122),否则新属性不具备响应式。
  • Mutation 必须是同步函数:devtools 需要在 mutation 提交后立即记录状态快照,异步操作会让快照无法追溯,也就无法做时间旅行调试。

2. Action ​

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

3. dispatch 返回 Promise ​

this.$store.dispatch 的返回值是 Promise,所以后面可以接 .then(),在 action 执行结束后再执行后续动作。

八、组合 Action 的使用 ​

1. 在组件里派发 action ​

js
this.$store.dispatch("actionA", value).then((res) => {
  // 派发其他的 action 或者写其他逻辑
});

2. 在 action 里派发另一个 action ​

js
const actions = {
  setCount({ commit }, value) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit("set_count", value);
        console.log("3");
        resolve();
      }, 1000);
    });
  },

  actionA({ dispatch, commit }, value) {
    return dispatch("setCount").then(() => {
      commit("action_1", value);
      console.log("4");
    });
  },
};

export default actions;

3. 使用 async 与 await ​

js
const actions = {
  action({ commit }, value) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        commit("action_1", value);
        console.log("4");
        resolve();
      }, 500);
    });
  },

  async actionB({ dispatch, commit }, value) {
    await dispatch("action", value + 1);
    commit("set_count", value);
  },
};

export default actions;

九、Module 命名空间 ​

上面都是单一状态树,当状态比较大时可以模块化。

未开启命名空间(namespaced: false) ​

js
console.log(this.$store);

modules 只有一个 root 根属性(全局属性),新增的 themeColor 模块挂在它的 children 下,mutations 中的方法 set_echartFontColor 也都放在 _mutations 中。

未开启命名空间时的 modules 结构

开启命名空间(namespaced: true) ​

_modulesNamespaceMap 中会登记该模块,mutations 中的方法 set_echartFontColor 在 _mutations 中自动加上 themeColor/ 前缀,从而与全局方法区分开来。

开启命名空间时的 modules 结构

用函数声明模块状态 ​

如果使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间的数据互相污染。

实际上这和 Vue 组件内的 data 是同样的问题,解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

js
const MyReusableModule = {
  state: () => ({
    foo: "bar",
  }),
  // mutation、action 和 getter 等等...
};