Vuex冷门知识点和踩坑记录

vuex在使用过程中的一些注意点,以及实际项目中踩过的坑。

1. 踩坑 - 业务添加state属性

  需求:
  在组件中调用一个异步请求,不改变已有的state中的属性,而是在添加一个新的属性(该属性未在state中声明过)。

Home.vue

<template>
  <div class="home">
    {{ getName }}
  </div>
</template>

<script>
import { mapGetters } from "vuex";

export default {
  name: "home",
  components: {},
  computed: {
    ...mapGetters([
      "getName"
    ])
  },
  created() {
    setTimeout(() => {
      // 新添加一个属性
      this.$store.dispatch("addProps", "jack");
    }, 1000);
  }
};
</script>

  代码编写:

store.js

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

Vue.use(Vuex);

let store = new Vuex.Store({
  state: {
    num: 1
  },
  getters: {
    getName(state) {
      return state.name;
    }
  },
  mutations: {
    addProps(state, name) {
      state.name = name;
    }
  },
  actions: {
    // 添加属性
    addProps({ commit }, name) {
      commit("addProps", name);
    }
  }
});
export default store;

  现象:
  通过Chrome插件Vue Devtools可以看到state中确实添加了name属性,并且有值,但是页面中不显示

  代码修改:
  如果要添加一个当前state中没有的属性,vue要将某一个属性作为响应式的处理,必须要通过 Object.defineProperty 的操作。
  这样的行为其实在一开始state里面已经做了,在最开始拿到state对象后,vue就做了已定义的属性的监视。
  下面修改 mutations 里的代码:

store.js

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

Vue.use(Vuex);

let store = new Vuex.Store({
  state: {
    num: 1
  },
  getters: {
    getName(state) {
      return state.name;
    }
  },
  mutations: {
    addProps(state, name) {
      // Object.defineProperty 实现响应式(双向数据绑定)
      if (!state.name) {
        // 实例对象用 .$xxx
        // 构造函数用 .xxx
        // 引入Vue这个构造函数对象
        Vue.set(state, "name", name);
      } else {
        state.name = name;
      }
    }
  },
  actions: {
    // 添加属性
    addProps({ commit }, name) {
      commit("addProps", name);
    }
  }
});
export default store;

2. 细节 - 模块化后的模块层级

  背景:
  Vuex 强调使用单一状态树,即在一个项目里只有一个 store,这个 store 集中管理了项目中所有的数据以及对数据的操作行为。但是这样带来的问题是 store 可能会非常臃肿庞大不易维护,所以就需要对状态树进行模块化的拆分。
  所谓模块化,就是除了根 store 以外,还通过 module 将各组件的 store 分开管理,下面我们来看一下代码。

  基本的组件化规则:你可以根据项目组件的划分来拆分 store,每个模块里管理着当前组件的状态以及行为,最后将这些模块在根 store 进行组合。

store.js

const moduleA = {
    state: { ... },
    getters: { ... },
    mutations: { ... }
};

const moduleB = {
    state: { ... },
    getters: { ... },
    mutations: { ... },
    actions: { ... }
};

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

console.log(store.state.a); // moduleA 的 state

  注意事项:
  接下来看 Vuex 核心在模块化后的使用注意事项。

  State
  在 Vuex 模块化中,state 是唯一会根据组合时模块的别名来添加层级的,后面的 getters、mutations 以及 actions 都是直接合并在 store 下。
  例如,访问模块 a 中的 state,要通过 store.state.a,访问根 store 上申明的 state,依然是通过 store.state.xxx 直接访问。

const moduleA = {
    state: {
        maState: 'A'
    }
};

const moduleB = {
    state: {
        mbState: 'B'
    }
};

const store = new Vuex.Store({
    modules: {
        a: moduleA,
        b: moduleB
    },
    state: {
        rtState: 'Root'
    }
});

console.log(store.state.a.maState); // A
console.log(store.state.b.mbState); // B
console.log(store.state.rtState); // Root

  Getters
  与 state 不同的是,不同模块的 getters 会直接合并在 store.getters 下。

const moduleA = {
    state: {
        count: 1
    },
    getters: {
        maGetter(state, getters, rootState) {
            return state.count + rootState.b.count;
        }
    }
};

const moduleB = {
    state: {
        count: 2
    },
    getters: {
        mbGetter() {
            return 'Hello Vuex';
        }
    }
};

const store = {
    modules: {
        a: moduleA,
        b: moduleB
    }
};

console.log(store.getters.maGetter);
console.log(store.getters.mbGetter); // Hello Vuex

  我们知道 getters 的回调函数所接收的前两个参数分别为 state 和 其他getter,模块化后需要用到第三个参数:rootState。
  (参数:1. state,模块中的 state 仅为模块自身中的 state;2. getters,等同于 store.getters;3. rootState,全局 state)
  通过 rootState,模块中的 getters 就可以引用别的模块中的 state 了,十分方便。
  注意:由于 getters 不区分模块,所以不同模块中的 getters 如果重名,Vuex 会报出 ‘duplicate getter key: [重复的getter名]‘ 错误。

  Mutations
  mutations 与 getters 类似,不同模块的 mutation 均可以通过 store.commit 直接触发。

const moduleA = {
    state: {
        count: 1
    },
    mutations: {
        sayCountA(state) {
            console.log('Module A count: ', state.count);
        }
    }
};

const moduleB = {
    state: {
        count: 2
    },
    mutations: {
        sayCountB(state) {
            console.log('Module B count: ', state.count);
        }
    }
};

const store = {
    modules: {
        a: moduleA,
        b: moduleB
    }
};

store.commit('sayCountA'); // Module A count: 1
store.commit('sayCountB'); // Module B count: 2 

  mutation 的回调函数中只接收唯一的参数——当前模块的 state。如果不同模块中有同名的 mutation,Vuex 不会报错,通过 store.commit 调用,会依次触发所有同名 mutation。

  Actions
  与 mutations 类似,不同模块的 actions 均可以通过 store.dispatch 直接触发。

const moduleA = {
    state: {
        count: 1
    },
    mutations: {
        sayCountA(state) {
            console.log('Module A count: ', state.count);
        }
    },
    actions: {
        maAction(context) {
            context.dispatch('mbAction');
        }
    }
};

const moduleB = {
    state: {
        count: 2
    },
    mutations: {
        sayCountB(state, num) {
            console.log('Module B count: ', state.count+num);
        }
    },
    action: {
        mbAction({ commit, rootState }) {
            commit('sayCountA');
            commit('sayCountB', rootState.a.count);
        }
    }
};

const store = {
    modules: {
        a: moduleA,
        b: moduleB
    }
};

store.dispatch('maAction'); // Module A count: 1、Module B count: 3

  从上例可以看出,action 的回调函数接收一个 context 上下文参数,
  context 包含:1. state、2. rootState、3. getters、4. mutations、5. actions 五个属性,为了简便可以在参数中解构。
  在 action 中可以通过 context.commit 跨模块调用 mutation,同时一个模块的 action 也可以调用其他模块的 action。
  同样的,当不同模块中有同名 action 时,通过 store.dispatch 调用,会依次触发所有同名 actions。
  最后有一点要注意的是,将 store 中的 state 绑定到 Vue 组件中的 computed 计算属性后,对 state 进行更改需要通过 mutation 或者 action,在 Vue 组件中直接进行赋值 (this.myState = ‘ABC’) 是不会生效的。

未完待续…

  后期如果踩过其他有意思的坑或者发现较为冷门的知识点,再更新这篇文章。


参考
https://juejin.im/post/5c23a440e51d457054162479


  目录