vuex 依赖于promise

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. /*
  4. vuex 依赖于promise
  5. */
  6. Vue.use(Vuex)

Stage

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 0
  4. },
  5. mutations: {
  6. increment (state) {
  7. state.count++
  8. }
  9. }
  10. })
  11. store.commit('increment')
  12. console.log(store.state.count) // -> 1

为了在 Vue 组件中访问this.$storeproperty,你需要为 Vue 实例提供创建好的 store。Vuex 提供了一个从根组件向所有子组件,以store选项的方式“注入”该 store 的机制

  1. new Vue({
  2. el: '#app',
  3. store: store,
  4. // ...
  5. methods: {
  6. increment() {
  7. this.$store.commit('increment')
  8. console.log(this.$store.state.count)
  9. }
  10. }
  11. })

mapState

  1. // ...
  2. computed: mapState({
  3. // 箭头函数可使代码更简练
  4. count: state => state.count,
  5. // 传字符串参数 'count' 等同于 `state => state.count`
  6. countAlias: 'count',
  7. // 为了能够使用 `this` 获取局部状态,必须使用常规函数
  8. countPlusLocalState (state) {
  9. return state.count + this.localCount
  10. }
  11. })
  12. computed: {
  13. localComputed () { /* ... */ },
  14. // 使用对象展开运算符将此对象混入到外部对象中
  15. ...mapState({
  16. // ...
  17. })
  18. }

Getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
Getter 接受 state 作为其第一个参数:

  1. const store = new Vuex.Store({
  2. state: {
  3. todos: [
  4. { id: 1, text: '...', done: true },
  5. { id: 2, text: '...', done: false }
  6. ]
  7. },
  8. getters: {
  9. doneTodos: state => {
  10. return state.todos.filter(todo => todo.done)
  11. }
  12. }
  13. })

通过属性访问

  1. store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter 也可以接受其他 getter 作为第二个参数:

  1. getters: {
  2. // ...
  3. doneTodosCount: (state, getters) => {
  4. return getters.doneTodos.length
  5. }
  6. }
  7. // ...
  8. store.getters.doneTodosCount // -> 1

通过方法访问
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

  1. getters: {
  2. // ...
  3. getTodoById: (state) => (id) => {
  4. return state.todos.find(todo => todo.id === id)
  5. }
  6. }
  7. // 注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。
  8. store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

  1. computed: {
  2. // 使用对象展开运算符将 getter 混入 computed 对象中
  3. ...mapGetters([
  4. 'doneTodosCount',
  5. 'anotherGetter',
  6. // ...
  7. ])
  8. }

Mutation

vuex通过commit修改stage的值,你可以向store.commit 写入额外的入参
注意 ,Mutation 必须是同步函数

  1. mutations: {
  2. increment (state, n) { // 通常n会是一个对象
  3. state.count += n
  4. }
  5. }
  6. store.commit("increment", 10);
  7. // 可以用这种风格
  8. mutations: {
  9. increment (state, payload) {
  10. state.count += payload.amount
  11. }
  12. }
  13. store.commit({
  14. type: 'increment',
  15. amount: 10
  16. })

你可以在组件中使用this.$store.commit(‘xxx’)提交 mutation,或者使用mapMutations辅助函数将组件中的 methods 映射为store.commit调用(需要在根节点注入store)。

  1. import { mapMutations } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapMutations([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
  7. // `mapMutations` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
  9. ]),
  10. ...mapMutations({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
  12. })
  13. }
  14. }

Action

可以通过action处理异步,Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
  • Action 可以返回一个promise实例,完成链式调用action ```javascript const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) {
    1. state.count++
    } }, actions: { increment (context) { // action也可以接收第二个参数
    1. // context.state
    2. // context.getters
    3. context.commit('increment');
    4. // 可以返回promise实例
    5. return Promise.resolver();
    } } })

store.dispatch(‘increment’, { // action也可以接收第二个参数 amount: 10 })

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

  1. 调用异步 API和分发多重 mutation
  2. ```javascript
  3. actions: {
  4. checkout ({ commit, state }, products) {
  5. // 把当前购物车的物品备份起来
  6. const savedCartItems = [...state.cart.added]
  7. // 发出结账请求,然后乐观地清空购物车
  8. commit(types.CHECKOUT_REQUEST)
  9. // 购物 API 接受一个成功回调和一个失败回调
  10. shop.buyProducts(
  11. products,
  12. // 成功操作
  13. () => commit(types.CHECKOUT_SUCCESS),
  14. // 失败操作
  15. () => commit(types.CHECKOUT_FAILURE, savedCartItems)
  16. )
  17. }
  18. }

你在组件中使用this.$store.dispatch(‘xxx’)分发 action,或者使用mapActions辅助函数将组件的 methods 映射为store.dispatch调用(需要先在根节点注入store):

  1. import { mapActions } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapActions([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
  7. // `mapActions` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
  9. ]),
  10. ...mapActions({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
  12. })
  13. }
  14. }

promise

  1. actions: {
  2. async actionA ({ commit }) {
  3. commit('gotData', await getData())
  4. },
  5. async actionB ({ dispatch, commit }) {
  6. await dispatch('actionA') // 等待 actionA 完成
  7. commit('gotOtherData', await getOtherData())
  8. }
  9. }

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:

  1. const moduleA = {
  2. state: () => ({
  3. count: 0
  4. }),
  5. mutations: {
  6. add() {
  7. //...
  8. },
  9. ...
  10. },
  11. actions: { ... },
  12. getters: { ... }
  13. }
  14. const moduleB = {
  15. state: () => ({ ... }),
  16. mutations: { ... },
  17. actions: { ... }
  18. }
  19. const store = new Vuex.Store({
  20. modules: {
  21. a: moduleA,
  22. b: moduleB
  23. }
  24. })
  25. store.state.a // -> moduleA 的状态
  26. store.state.b // -> moduleB 的状态
  27. this.$store.commit('a/add',10)

模块内部的 action,局部状态通过context.state暴露出来,根节点状态则为context.rootState:
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:

  1. const moduleA = {
  2. // ...
  3. actions: {
  4. incrementIfOddOnRootSum ({ state, commit, rootState }) {
  5. if ((state.count + rootState.count) % 2 === 1) {
  6. commit('increment')
  7. }
  8. }
  9. }
  10. }
  11. const moduleA = {
  12. // ...
  13. getters: {
  14. sumWithRootCount (state, getters, rootState) {
  15. return state.count + rootState.count
  16. }
  17. }
  18. }

命名空间

命名空间

模块动态注册

  1. import Vuex from 'vuex'
  2. const store = new Vuex.Store({ /* 选项 */ })
  3. // 注册模块 `myModule`
  4. store.registerModule('myModule', {
  5. // ...
  6. })
  7. // 注册嵌套模块 `nested/myModule`
  8. store.registerModule(['nested', 'myModule'], {
  9. // ...
  10. })
  11. // 之后就可以通过 store.state.myModule 和 store.state.nested.myModule 访问模块的状态。
  12. // store.unregisterModule(moduleName) 来动态卸载模块 只能卸载动态的,不能卸载静态的。
  13. // 你可以通过 store.hasModule(moduleName) 方法检查该模块是否已经被注册到 store

在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过preserveState选项将其归档:store.registerModule(‘a’, module, { preserveState: true })。
当你设置preserveState: true时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。

dispatch 与 commit 的区别

Action 类似于 mutation,不同在于:
Action 提交的是 mutation,而不是直接变更状态。
Action 可以包含任意异步操作。
dispatch 推送一个action

commit 推送一个mutation

注意事项

模块重用

有时我们可能需要创建一个模块的多个实例,例如:

如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的data是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):

  1. const MyReusableModule = {
  2. state: () => ({
  3. foo: 'bar'
  4. }),
  5. // mutation, action 和 getter 等等...
  6. }
  7. new Vue({
  8. data: () => {
  9. return {
  10. ...
  11. }
  12. }
  13. })