Vuex

HCX大约 5 分钟状态管理VueVuex

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

// store/index.js
import { createStore } from 'vuex';

// 创建一个新的 store 实例
const store = createStore({
  state() {
    return {
      count: 0,
    };
  },
  mutations: {
    increment(state) {
      state.count++;
    },
  },
});

export default store;
// main.js
import store from './store/index';
createApp(App).use(store).mount('#app');

State 用法

通常一个应用只有一棵单一状态树,这样定位某个状态片段更加快速直接,调试时更容易获取当前 app 状态快照

state 常见写法

  • 计算属性
computed: {
  count(){
    return this.$store.state.count
  }
}
  • helper 方法:mapState,接收对象或者数组,返回一个对象 数组方式:最简单直接的方式
import { mapState } from 'vuex';

export default {
  computed: {
    // 映射 this.count 为 store.state.count
    ...mapState(['count'])...mapState({
      // 箭头函数可使代码更简练
      count: state => state.count,

      // 传字符串参数 'count' 等同于 `state => state.count`
      countAlias: 'count',

      // 为了能够使用 `this` 获取局部状态,必须使用常规函数
      countPlusLocalState (state) {
        return state.count + this.localCount
      }
    })
  },
};

派生状态 Getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。

const store = createStore({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false },
    ],
  },
  getters: {
    doneTodos(state) {
      return state.todos.filter(todo => todo.done);
    },
    // Getter 也可以接受其他 getter 作为第二个参数:
    doneTodosCount(state, getters) {
      return getters.doneTodos.length;
    },
    // 如果需要给Getter传参,需要通过返回函数来实现
    // 在使用时则被看作是一个方法
    nCount(state) {
      return n => {
        return state.count * n;
      };
    },
  },
});

同样 getter 也有 mapGetter 方法

Mutations

Mutations 是唯一改变状态的方式

定义 mutations

每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

const store = createStore({
  state: {
    count: 1,
  },
  mutations: {
    // 通过 commit 传递额外参数 n
    increment(state, n) {
      // 变更状态
      state.count += n;
    },
  },
});

传参还可以支持对象形式,此时属性 type 固定为事件类型

store.commit('increment', 10);
store.commit({
  type: "increamen"
  num: 10 // 在mutation 中通过第二个参数 payload 获取参数
});

相关信息

状态的变更必须是同步的,即 mutations 都应为同步

通常会新建一个文件 mutation-types 用于存放事件类型的常量名称

mapMutations 方法可以简写 commit 方法

import { mapMutations } from 'vuex';

export default {
  // ...
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy', // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment', // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    }),
  },
};

Action

动作类似于 Mutations,它们主要用于:

  • 实现复杂业务逻辑
  • 实现异步操作

定义 Action

action: {
  inc (context) {
    // 通过 context.state 和 context.getters 也可以来获取 state 和 getters
    setTimeOut(()=>{
      context.commit('inc')
    },1000)
  }
}

Action 通过 store.dispatch 方法触发:

// 以载荷形式分发
store.dispatch('incrementAsync', {
  amount: 10,
});

// 以对象形式分发
store.dispatch({
  type: 'incrementAsync',
  amount: 10,
});

同样有 mapActions 方法用于辅助映射

import { mapActions } from 'vuex';

export default {
  // ...
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy', // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment', // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    }),
  },
};

组合 Action store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise

// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
  async actionA ({ commit }) {
    commit('gotData', await getData())
  },
  async actionB ({ dispatch, commit }) {
    await dispatch('actionA') // 等待 actionA 完成
    commit('gotOtherData', await getOtherData())
  }
}

模块化

利用模块化拆分 store 定义避免状态树过大时难以维护

常见手法

const moduleA = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... }
}

const store = createStore({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

提示

进行模块化后,mapState 里面将会根据模块名称映射整个模块了。但由于模块仅仅是将 state 隔离了,所以 action、mutation、getters 仍然可以直接访问到

命名空间

模块化划分,仅仅将 state 隔离了,模块内部的 action 和 mutation 仍然是注册在全局命名空间的。意味着多个模块会同时响应相同的 action/mutation 类型,getters 也注册在全局命名空间,编写些注意不能重名

为了保证更加隔离和重用效果,最好给模块加上命名空间选项:namespace: true,这样所有 actions/mutations/getters 都将注册在独立的命名空间中

export default {
  namespaced: true,
};

访问命名空间后的 actions/mutations/getters

<script>
// 新增参数模块名称
computed: {
  ...mapGetters("moduleName", ["count"])
},
methods: {
  nCount(n) {
    // 通过动态属性的方式去访问到当前的 getters
    return this.$store.getters["count/nCount"](n);
  },
  ...mapMutations("count", ["inc", "incBy"]),
  ...maoActions("count", {
    incAsync: "inc"
  })
}
</script>

提示

在使用模块化后,模块内部也可以访问到根的状态和 getters。

如果你希望使用全局 state 和 getter,rootState 和 rootGetters 会作为第三和第四参数传入 getter,也会通过 context 对象的属性传入 action。

若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true } 作为第三参数传给 dispatch 或 commit 即可。

modules: {
  foo: {
    namespaced: true,

    getters: {
      // 在这个模块的 getter 中,`getters` 被局部化了
      // 你可以使用 getter 的第四个参数来调用 `rootGetters`
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
        rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // 在这个模块中, dispatch 和 commit 也被局部化了
      // 他们可以接受 `root` 属性以访问根 dispatch 或 commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'
        rootGetters['bar/someGetter'] // -> 'bar/someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}