在快应用开发中使用 vuex module
上一篇文章「在快应用开发中使用 Vuex」介绍了 Vuex 五个部分中的前四个部分:State、Getter、Mutation、Action 以及一些辅助函数,那么这篇文章就来介绍一下 Vuex 中的 module。Vuex 使用的是单一状态树,应用的所有状态会集中到一个对象中。如果项目比较大,那么相应的状态数据肯定就会更多,这样的话,store 对象就会变得相当的臃肿,非常难管理。所以这种情况下,我们就需要使用 vuex module。
单一状态树
Vuex 使用单一状态树来管理状态。所谓单一状态树,就是用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。当然,单一状态树和模块化是并不冲突的。
Vuex module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成大大小小的对象,每个对象也都拥有自己的 state、getter、mutation、action,这个对象我们把它叫做 module(模块),在模块中还可以继续嵌套子模块、子子模块 ……
引入 module
首先,新建一个 modules 文件夹,用于保存各个模块的状态。然后,新建一个 moduleA.js 文件
export default {
state: {
text: "moduleA"
},
getters: {
text(state) {
return "getter" + state.text;
}
},
mutations: {
setText(state) {
state.text = "set moduleA";
}
},
actions: {}
};
再新建一个 moduleB.js 文件,内容和 moduleA.js 类似就不再重复了。
然后在 store.js 导入 moduleA 和 moduleB
import Vuex from "quickapp-vuex";
import moduleA from "./modules/moduleA";
import moduleB from "./modules/moduleB";
export default new Vuex.Store({
modules: {
moduleA,
moduleB
}
});
之后,在 app.ux 文件中导入就可以在页面和组件中使用了。
<template>
<div class="demo-page">
<text class="title">{{ text1 }}</text>
<text class="title">{{ text2 }}</text>
<div>
<text class="btn" onclick="setText">更改文本</text>
</div>
</div>
</template>
<script>
import { mapMutations, Component, mapState } from "quickapp-vuex";
export default Component({
computed: {
...mapState({
text1: state => state.moduleA.text,
text2: state => state.moduleB.text
})
},
...mapMutations(["setText"])
});
</script>
我们在页面中使用 state.moduleA.text 和 state.moduleB.text 引入了 moduleA 和 moduleB 的 state,并映射为 text1 和 text2。由此可知,模块内部的 state 是局部的,只属于模块本身所有,所以外部必须通过对应的模块名进行访问。
但是需要注意了!模块内部的 action、mutation 和 getter 默认是注册在全局命名空间的,这样使得多个模块能够对同一 mutation 或 action 作出响应。比如,点击上面页面中的「更改文本」按钮,由于 moduleA 和 moduleB 都有一个名为 setText 的 mutation ,你会发现 name1 和 name2 的值都发生了改变,这可能并不是我们想要的结果。
命名空间
如上所述,模块内部的 action、mutation 和 getter 默认是注册在全局命名空间的。如果我们只想让他们在当前的模块中生效,通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
我们在 moduleA.js 中添加 namespaced: true
export default {
namespaced: true,
// ...
}
这样,在 moduleA 中定义的 action、mutation 和 getter 就只属于 moduleA 了。为了能在页面中获取到 moduleA 下的 mutation,我们需要修改一下代码
export default Component({
//...
...mapMutations({
call: "moduleA/setText"
})
});
上面,我们把 moduleA 模块下的 setText 映射为 call,现在,我们在点击「更改文本」按钮,就会发现只有 moduleA 的 state 发生了改变。
有时候,我们需要使用一个模块下的多个 mutation,如果每一次都要加上一长串模块名未免有些繁琐,所以 Vuex 提供了一种更简单的写法
export default Component({
computed: {
...mapState("some/nested/module", {
a: state => state.a,
b: state => state.b
})
},
...mapMutations("some/nested/module", ["foo", "bar"])
});
将模块的空间名称字符串作为第一个参数传递给辅助函数,这样所有绑定都会自动将该模块作为上下文。
如果觉得需要对每个辅助函数都绑定一次命名空间也有些繁琐,那么我们还可以通过使用 createNamespacedHelpers
创建基于某个命名空间的辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的辅助函数
import Vuex from "quickapp-vuex";
const { mapGetters, mapMutations } = Vuex.createNamespacedHelpers(
"some/nested/module"
);
export default Vuex.Component({
computed: {
// 在 `some/nested/module` 中查找
...mapState({
a: state => state.a,
b: state => state.b
})
},
methods: {
// 在 `some/nested/module` 中查找
...mapActions(["foo", "bar"])
}
});
访问根节点数据
我们已经知晓,模块内部的 state 是局部的,只属于模块本身所有。那么如果我们要想在模块中访问 store 根节点的数据 state,该怎么办呢?很简单,我们可以在模块内部的 getter 和 action 中,通过 rootState 这个参数来获取。
export default {
// ...
getters: {
// 注意:rootState必须是第三个参数
text(state, getters, rootState) {
return state.text + '-' + rootState.text;
}
},
actions: {
callAction({state, rootState}) {
console.log(state.text, rootState.text);
}
}
}
这里需要注意的是在 getters 中,rootState 是以第三个参数暴露出来的,另外,还有第四个参数 rootGetters,用来获得根节点的 getters 信息。而在 action 中,由于它接收过来的数据都被包在 context
对象中的,通过解构获取 rootState 则没有什么顺序的限制。
除了访问根节点的数据,如果想要在模块内分发全局 action 或提交全局 mutation 的话,那么我们只需要将 { root: true }
作为第三参数传给 dispatch 或 commit 即可。
export default {
namespaced: true,
// ...
actions: {
callAction({ state, commit, rootState }) {
commit("setName", "改变", { root: true });
console.log(state.text, rootState.text);
}
}
};
同时,若需要在带命名空间的模块注册全局 action,你可添加 root: true
,并将这个 action 的定义放在函数 handler 中
export default {
namespaced: true,
// ...
actions: {
callAction: {
root: true,
handler(namespacedContext, payload) {
let { state, commit } = namespacedContext;
commit("setText"); //这个mutation是模块内的mutation
console.log(state.text);
}
}
}
};
这里的 namespacedContext
就相当于当前模块的上下文对象,payload
是调用的时候所传入的参数,也就是载荷。然后,你可以在全局命名空间内引入callAction
,当你 dispatch 这个 Action 后,模块命名空间内的数据将被修改。也就是说,在带命名空间的模块注册全局 action 提供了一种在全局命名空间内访问模块命名空间的方式。