vuex 是一个专为 vue.js 设计的状态管理库, 适用于多组件共享状态的场景. Vuex 能集中式的存储和维护所有组件的状态, 并提供相关规则保证状态的独立性, 正确性和可预测性, 这不仅让调试变得可追踪, 还让代码变得更结构化且易维护. 本文所使用的 Vuex, 其版本是 3.1.1.
一, 基本用法
首先需要引入 Vue 和 Vuex 两个库, 如果像下面这样在 Vue 之后引入 Vuex, 那么 Vuex 会自动调用 Vue.use()方法注册其自身; 但如果以模块的方式引用, 那么就得显式地调用 Vue.use(). 注意, 因为 Vuex 依赖 Promise, 所以对于那些不支持 Promise 的浏览器, 要使用 Vuex 的话, 得引入相关的 polyfill 库, 例如 es6-promise.
- <script src="js/vue.js">
- </script>
- <script src="js/vuex.js">
- </script>
然后创建 Vuex 应用的核心: Store(仓库). 它是一个容器, 保存着大量的响应式状态 (State), 并且这些状态不能直接修改, 需要显式地将修改请求提交到 Mutation(变更) 中才能实现更新, 因为这样便于追踪每个状态的变化. 在下面的示例中, 初始化了一个 digit 状态, 并在 mutations 选项中添加了两个可将其修改的方法.
- const store = new Vuex.Store({
- state: {
- digit: 0
- },
- mutations: {
- add: state => state.digit++,
- minus: state => state.digit--
- }
- });
接着创建根实例, 并将 store 实例注入, 从而让整个应用都能读写其中的状态, 在组件中可通过 $store 属性访问到它, 如下所示, 以计算属性的方式读取 digit 状态, 并通过调用 commit()方法来修改该状态.
- var vm = new Vue({
- el: "#container",
- store: store,
- computed: {
- digit() {
- return this.$store.state.digit;
- }
- },
- methods: {
- add() {
- this.$store.commit("add");
- },
- minus() {
- this.$store.commit("minus");
- }
- }
- });
最后将根实例中的方法分别注册到两个按钮的点击事件中, 如下所示, 每当点击这两个按钮时, 状态就会更新, 并在页面中显示.
- <div id="container">
- <p>{{digit}}</p>
- <button @click="add">增加</button>
- <button @click="minus">减少</button>
- </div>
二, 主要组成
Vuex 的主要组成除了上一节提到的 Store,State 和 Mutation 之外, 还包括 Getter 和 Action, 本节会对其中的四个做重点讲解, 它们之间的关系如图 2 所示.
图 2 四者的关系
1)State
State 是一个可存储状态的对象, 在应用的任何位置都能被访问到, 并且作为单一数据源 (Single Source Of Truth) 而存在.
当组件需要读取大量状态时, 一个个的声明成计算属性会显得过于繁琐而冗余, 于是 Vuex 提供了一个名为 mapState()的辅助函数, 用来将状态自动映射成计算属性, 它的参数既可以是数组, 也可以是对象.
当计算属性的名称与状态名称相同, 并且不需要做额外处理时, 可将名称组成一个字符串数组传递给 mapState()函数, 在组件中可按原名调用, 如下所示.
- var vm = new Vue({
- computed: Vuex.mapState([ "digit" ])
- });
当计算属性的名称与状态名称不同, 或者计算属性读取的是需要处理的状态时, 可将一个对象传递给 mapState()函数, 其键就是计算属性的名称, 而其值既可以是函数, 也可以是字符串, 如下代码所示. 如果是函数, 那么它的第一个参数是 state, 即状态对象; 如果是字符串, 那么就是从 state 中指定一个状态作为计算属性.
- var vm = new Vue({
- computed: Vuex.mapState({
- digit: state => state.digit,
- alias: "digit" // 相当于 state => state.digit
- })
- });
因为 mapState()函数返回的是一个对象, 所以当组件内已经包含计算属性时, 可以对其应用扩展运算符 (...) 来进行合并, 如下所示, 这是一种极为简洁的写法.
- var vm = new Vue({
- computed: {
- name() {},
- ...Vuex.mapState([ "digit" ])
- }
- });
- 2)Getter
Getter 是从 State 中派生出的状态, 当多个组件要对同一个状态进行相同的处理时, 就需要将状态转移到 Getter 中, 以免产生重复的冗余代码.
Getter 相当于 Store 的计算属性, 它能接收两个参数, 第一个是 state 对象, 第二个是可选的 getters 对象, 该参数能让不同的 Getter 之间相互访问. Getter 的返回值会被缓存, 并且只有当依赖值发生变化时才会被重新计算. 不过当返回值是函数时, 其结果就不会被缓存, 如下所示, 其中 caculate 返回的是个数字, 而 sum 返回的是个函数.
- const store = new Vuex.Store({
- state: {
- digit: 0
- },
- getters: {
- caculate: state => {
- return state.digit + 2;
- },
- sum: state => right => {
- return state.digit + right;
- }
- }
- });
在组件内可通过 this.$store.getters 访问到 Getter 中的数据, 如下所示, 读取了上一个示例中的两个 Getter.
- var vm = new Vue({
- methods: {
- add() {
- this.$store.getters.caculate;
- this.$store.getters.sum(1);
- }
- }
- });
Getter 也有一个辅助函数, 用来将 Getter 自动映射为组件的计算属性, 名字叫 mapGetters(), 其参数也是数组或对象. 但与之前的 mapState()不同, 当参数是对象时, 其值不能是函数, 只能是字符串, 如下所示, 为了对比两种写法, 声明了两个 computed 选项.
- var vm = new Vue({
- computed: Vuex.mapGetters([ "caculate" ]),
- computed: Vuex.mapGetters({
- alias: "caculate"
- })
- });
- 3)Mutation
更改状态的唯一途径是提交 Mutation,Vuex 中的 Mutation 类似于事件, 也包含一个类型和回调函数, 在函数体中可进行状态更改的逻辑, 并且它能接收两个参数, 第一个是 state 对象, 第二个是可选的附加数据, 叫载荷(Payload). 下面这个 Mutation 的类型是 "interval", 接收了两个参数.
- const store = new Vuex.Store({
- state: {
- digit: 0
- },
- mutations: {
- interval: (state, payload) => state.digit += payload.number
- }
- });
在组件中不能直接调用 Mutation 的回调函数, 得通过 this.$store.commit()方法触发更新, 如下所示, 采用了两种提交方式, 第一种是传递 type 参数, 第二种是传递包含 type 属性的对象.
- var vm = new Vue({
- methods: {
- interval() {
- this.$store.commit("interval", { number: 2 }); // 第一种
- this.$store.commit({ type: "interval", number: 2 }); // 第二种
- }
- }
- });
当多人协作时, Mutation 的类型适合写成常量, 这样更容易维护, 也能减少冲突.
const INTERVAL = "interval";
Mutation 有一个名为 mapMutations()的辅助函数, 其写法和 mapState()相同, 它能将 Mutation 自动映射为组件的方法, 如下所示.
- var vm = new Vue({
- methods: Vuex.mapMutations(["interval"])
- // 相当于
- methods: {
- interval(payload) {
- this.$store.commit(INTERVAL, payload);
- }
- }
- });
注意, 为了能追踪状态的变更, Mutation 只支持同步的更新, 如果要异步, 那么得使用 Action.
4)Action
Action 类似于 Mutation, 但不同的是它可以包含异步操作, 并且只能用来通知 Mutation, 不会直接更新状态. Action 的回调函数能接收两个参数, 第一个是与 Store 实例具有相同属性和方法的 context 对象 (注意, 不是 Store 实例本身), 第二个是可选的附加数据, 如下所示, 调用 commit() 方法提交了一个 Mutation.
- const store = new Vuex.Store({
- actions: {
- interval(context, payload) {
- context.commit("interval", payload);
- }
- }
- });
在组件中能通过 this.$store.dispatch()方法分发 Action, 如下所示, 与 commit()方法一样, 它也有两种提交方式.
- var vm = new Vue({
- methods: {
- interval() {
- this.$store.dispatch("interval", { number: 2 }); // 第一种
- this.$store.dispatch({type: "interval", number: 2}); // 第二种
- }
- }
- });
注意, 由于 dispatch()方法返回的是一个 Promise 对象, 因此它能以一种更优雅的方式来处理异步操作, 如下所示.
- var vm = new Vue({
- methods: {
- interval() {
- this.$store.dispatch("interval", { number: 2 }).then(() => {
- console.log("success");
- });
- }
- }
- });
Action 有一个名为 mapActions()的辅助函数, 其写法和 mapState()相同, 它能将 Action 自动映射为组件的方法, 如下所示.
- var vm = new Vue({
- methods: Vuex.mapActions([ "interval" ])
- });
三, 模块
当应用越来越大时, 为了避免 Store 变得过于臃肿, 有必要将其拆分到一个个的模块 (Module) 中. 每个模块就是一个对象, 包含属于自己的 State,Getter,Mutation 和 Action, 甚至还能嵌套其它模块.
1)局部状态
对于模块内部的 Getter 和 Mutation, 它们接收的第一个参数是模块的局部状态, 而 Getter 的第三个参数 rootState 和 Action 的 context.rootState 属性可访问根节点状态(即全局状态), 如下所示.
- const moduleA = {
- state: { digit: 0 },
- mutations: {
- add: state => state.digit++
- },
- getters: {
- caculate: (state, getter, rootState) => {
- return state.digit + 2;
- }
- },
- actions: {
- interval(context, payload) {
- context.commit("add", payload);
- }
- }
- };
2)命名空间
默认情况下, 只有在访问 State 时需要带命名空间, 而 Getter,Mutation 和 Action 的调用方式不变. 将之前的 moduleA 模块注册到 Store 实例中, 如下所示, modules 选项的值是一个子模块对象, 其键是模块名称.
- const store = new Vuex.Store({
- modules: {
- a: moduleA
- }
- });
如果要访问模块中的 digit 状态, 那么可以像下面这样写.
store.state.a.digit;
当模块的 namespaced 属性为 true 时, 它的 Getter,Mutation 和 Action 也会带命名空间, 在使用时, 需要添加命名空间前缀, 如下代码所示, 此举大大提升了模块的封装性和复用性.
- const moduleA = {
- namespaced: true
- };
- var vm = new Vue({
- el: "#container",
- store: store,
- methods: {
- add() {
- this.$store.commit("a/add");
- },
- caculate() {
- this.$store.getters["a/caculate"];
- }
- }
- });
如果要在带命名空间的模块中提交全局的 Mutation 或分发全局的 Action, 那么只要将 {root: true} 作为第三个参数传给 commit()或 dispatch()就可实现, 如下所示.
- var vm = new Vue({
- methods: {
- add() {
- this.$store.dispatch("add", null, { root: true });
- this.$store.commit("add", null, { root: true });
- }
- }
- });
如果要在带命名空间的模块中注册全局的 Action, 那么需要将其修改成对象的形式, 然后添加 root 属性并设为 true, 再将 Action 原先的定义转移到 handler()函数中, 如下所示.
- const moduleA = {
- actions: {
- interval: {
- root: true,
- handler(context, payload) {}
- }
- }
- };
3)辅助函数
当使用 mapState(),mapGetters(),mapMutations()和 mapActions()四个辅助函数对带命名空间的模块做映射时, 需要显式的包含命名空间, 如下所示.
- var vm = new Vue({
- computed: Vuex.mapState({
- digit: state => state.a.digit
- }),
- methods: Vuex.mapMutations({
- add: "a/add"
- })
- });
这四个辅助函数的第一个参数都是可选的, 用于绑定命名空间, 可简化映射过程, 如下所示.
- var vm = new Vue({
- computed: Vuex.mapState("a", {
- digit: state => state.digit
- }),
- methods: Vuex.mapMutations("a", {
- add: "add"
- })
- });
Vuex 还提供了另一个辅助函数 createNamespacedHelpers(), 可创建绑定命名空间的辅助函数, 如下所示.
const { mapState, mapMutations } = Vuex.createNamespacedHelpers("a");
四, 动态注册
在创建 Store 实例后, 可通过 registerModule()方法动态注册模块, 如下代码所示, 调用了两次 registerModule()方法, 第一次注册了模块 "a", 第二次注册了嵌套模块 "a/b".
- const store = new Vuex.Store();
- store.registerModule("a", moduleA);
- store.registerModule(["a", "b"], moduleAB);
通过 store.state.a 和 store.state.a.b 可访问模块的局部状态. 如果要卸载动态注册的模块, 那么可以通过 unregisterModule()方法实现.
registerModule()方法的第三个参数是可选的配置对象, 当 preserveState 属性的值为 true 时(如下所示), 在注册模块时会忽略模块中的状态, 即无法在 store 中读取模块中的状态.
store.registerModule("a", moduleA, { preserveState: true });
五, 表单处理
表单默认能直接修改组件的状态, 但是在 Vuex 中, 状态只能由 Mutation 触发更新. 为了能更好的追踪状态的变化, 也为了能更符合 Vuex 的思维, 需要让表单控件与状态绑定在一起, 并通过 input 或 change 事件监听状态更新的行为, 如下所示.
- <div id="container">
- <input :value="digit" @input="add" />
- </div>
然后在 Store 实例中初始化 digit 状态, 并添加更新状态的 Mutation, 如下所示.
const store = new Vuex.Store({ state: { digit: 0 }, mutations: { add: (state, value) => { state.digit = value; } } });
最后在创建根实例时, 将 digit 状态映射成它的计算属性, 在事件处理程序 add()中调用 commit()方法, 并将控件的值作为第二个参数传入, 如下所示.
var vm = new Vue({ el: "#container", store: store, computed: Vuex.mapState(["digit"]), methods: { add(e) { this.$store.commit("add", e.target.value); } } });
还有一个方法也能实现相同的功能, 那就是在控件上使用 v-model 指令, 但需要与带 setter 的计算属性配合, 如下所示(只给出了关键部分的代码).
<div id="container"> <input v-model="digit" /> </div> <script> var vm = new Vue({ computed: { digit: { get() { return this.$store.state.digit; }, set(value) { this.$store.commit("add", value); } } } }); </script>
来源: https://www.cnblogs.com/strick/p/11535567.html