mutation

通过 state 的学习,我们知道了如何在 Vue 组件中获取 state,那么如何在 Vue 组件中修改 state 呢?

如前文所述,更改 Vuexstore 中的状态的唯一方法是提交 mutationVuex 中的 mutation 类似于事件:每个 mutation 都有一个字符串作为事件类型(type)和一个回调函数(handler)。这个回调函数就是实际进行状态更改的地方,并且它会接收 state 作为第一个参数,如示例代码 6-5-1 所示。

const store = Vuex.createStore({
    state: {
        count: 3
    },
    mutations: {
        increment(state, params) {
            state.count = state.count + params.num
        }
    }
})
const counter = {
    template: '<div>{{ count }}<button @click="clb">增加</button></div>',
    computed: {
        count () {
            return this.$store.state.count // 通过this.$store.state可以获取state
        }
    },
    methods:{
        clb(){
            // 通过this.$store.commit调用mutations
            this.$store.commit('increment', {
                num: 4
            })
        }
    }
}

在调用 this.$store.commit 时,第一个参数是在 store 中定义的 mutations 的一个 key 值,即 'increment';第二个参数是自定义传递的数据,然后在 storemutations 方法中就可以获取该数据。

提交 mutation 的另一种方式是直接使用包含 type 属性的对象,代码如下:

...
this.$store.commit({
    type: 'increment',
    num: 4
})
...

同样会调用 increment 这个 handler 方法,然后可以从第二个参数中获取 num 值,整个 handler 方法没有变化:

...
increment(state, params) {
    state.count = state.count + params.num
}
...

同样,和 getters 一样,在组件中使用 mutations 时,可以用 mapMutations 辅助函数来快速在 methods 中映射,如示例代码 6-5-2 所示。

示例代码 6-5-2 mapMutations
const store = Vuex.createStore({
    state: {
        count: 3
    },
    mutations: {
        increment(state, params) {
            state.count = state.count + params.num
        }
    }
})

const counter = {
    template: '<div>{{ count }}<button @click="clb({num:4})">增加</button></div>',
    computed: {
        count () {
            return this.$store.state.count // 通过 this.$store.state 可以获取 state
        },
        methods: {
            ...Vuex.mapMutations({
                clb: 'increment' // 将 this.clb() 映射为 this.$store.commit('increment')
            })
        }
    }
}

注意,mapMutations 只是将 clb 方法和 this.$store.commit('increment') 进行映射,对于 increment 方法中的参数是没有改动的,clb 方法里面的参数可以直接进行传递,如 clb({num:4})

另外,一条重要的原则就是要记住 mutation 必须是同步函数。如果这样编写,就会产生一个异步函数调用:

mutations: {
    someMutation (state) {
        setTimeout(()=> {
            state.count++
        },1000)
    }
}

在回调函数中触发 state.count++ 时,可以看到在延时了 1 秒之后,状态改变了,这看起来确实可以达到效果,但是 Vue 并不推荐这样做。

可以想象一下,当我们正在使用 DevTools 工具调试一个 Vuex 应用,并且正在观察 DevTools 中的 mutation 日志时,正常情况下每一条 mutation 都被正常记录,需要捕捉到前一个状态和后一个状态的快照。然而,在上面的例子中,mutation 中异步函数内的回调打破了这种机制,让调试工作不可能完成:因为当 mutation 触发时,回调函数还没有被调用,DevTools 不知道什么时候回调函数被真正调用,实质上任何在回调函数中进行的状态改变都是不可追踪的。

mutation 中混合异步调用会导致程序很难调试。例如,当调用了两个包含异步回调的 mutation 来改变状态时,我们无法知道什么时候回调和哪个先回调,这就是为什么要区分这两个概念的原因。在 Vuex 中,mutation 都是同步事务,为了解决异步问题,需要引入 action

在 Vuex 中,mapMutations 是一个帮助函数,用于将 Vuex store 中的 mutations 映射到组件的方法中。这使得你可以在组件中更方便地调用 storemutations,从而改变 store 的状态。

为什么使用 mapMutations

Mutations 是 Vuex 中唯一能够同步改变状态的方法。在组件中使用 mapMutations 可以简化调用 mutations 的过程,使代码更简洁、可读性更高。

基本用法

mapMutations 可以将 Vuex store 中的 mutations 映射为组件的方法。它有两种主要用法:

  1. 使用字符串数组:直接映射 store 中的 mutations

  2. 使用对象:重命名方法或创建带参数的方法。

使用字符串数组

假设你的 Vuex store 中有如下 mutations

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  }
});

在组件中,你可以使用 mapMutations 将这些 mutations 映射为组件的方法:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
import { mapMutations, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations(['increment', 'decrement'])
  }
};
</script>

在这个示例中,incrementdecrement mutations 被映射为组件的方法,可以直接在模板中使用。

使用对象

当你需要重命名方法或传递参数时,可以使用对象形式:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="incrementCount">Increment</button>
    <button @click="decrementCount">Decrement</button>
    <button @click="incrementBy(5)">Increment by 5</button>
  </div>
</template>

<script>
import { mapMutations, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations({
      incrementCount: 'increment', // 将 `increment` mutation 映射为 `incrementCount`
      decrementCount: 'decrement', // 将 `decrement` mutation 映射为 `decrementCount`
      incrementBy(state, payload) {
        this.$store.commit('incrementBy', payload);
      }
    })
  }
};
</script>

在这个示例中:

  • incrementCountdecrementCount 方法分别映射了 store 中的 incrementdecrement mutations

  • incrementBy 方法接收一个参数并调用相应的 mutation

为了支持 incrementBy 方法,需要在 Vuex store 中定义相应的 mutation

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
    incrementBy(state, payload) {
      state.count += payload;
    }
  }
});

结合局部方法

如果组件中既需要使用 Vuex storemutations,又需要使用组件的局部方法,可以将它们结合起来:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
    <button @click="localMethod">Local Method</button>
  </div>
</template>

<script>
import { mapMutations, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations(['increment', 'decrement']),
    localMethod() {
      console.log('This is a local method');
    }
  }
};
</script>

在这个示例中,组件既有来自 Vuex storemutations 方法,也有组件自己的 localMethod 方法。

模块化 Vuex

在使用 Vuex 模块化时,可以指定模块名称来访问嵌套的 mutations

const store = new Vuex.Store({
  modules: {
    myModule: {
      state: () => ({
        count: 0
      }),
      mutations: {
        increment(state) {
          state.count++;
        },
        decrement(state) {
          state.count--;
        }
      }
    }
  }
});

在组件中:

<template>
  <div>
    <p>Module Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
import { mapMutations, mapState } from 'vuex';

export default {
  computed: {
    ...mapState('myModule', ['count'])
  },
  methods: {
    ...mapMutations('myModule', ['increment', 'decrement'])
  }
};
</script>

在这个示例中,myModule 模块中的 incrementdecrement mutations 被映射为组件的方法。

mapMutationsVuex 提供的一个非常方便的工具,可以简化在组件中调用 store mutations 的过程。通过字符串数组和对象的两种形式,你可以灵活地映射 storemutations 到组件的方法中,并在需要时结合局部方法和模块化的 store 使用。这样,你的代码将更加清晰、简洁,并且易于维护。