mutation
通过 state
的学习,我们知道了如何在 Vue 组件中获取 state
,那么如何在 Vue
组件中修改 state
呢?
如前文所述,更改 Vuex
的 store
中的状态的唯一方法是提交 mutation
。Vuex
中的 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'
;第二个参数是自定义传递的数据,然后在 store
的 mutations
方法中就可以获取该数据。
提交 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 所示。
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
映射到组件的方法中。这使得你可以在组件中更方便地调用 store
的 mutations
,从而改变 store
的状态。
为什么使用 mapMutations
Mutations
是 Vuex 中唯一能够同步改变状态的方法。在组件中使用 mapMutations
可以简化调用 mutations
的过程,使代码更简洁、可读性更高。
基本用法
mapMutations
可以将 Vuex store
中的 mutations
映射为组件的方法。它有两种主要用法:
-
使用字符串数组:直接映射
store
中的mutations
。 -
使用对象:重命名方法或创建带参数的方法。
使用字符串数组
假设你的 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>
在这个示例中,increment
和 decrement 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>
在这个示例中:
-
incrementCount
和decrementCount
方法分别映射了store
中的increment
和decrement 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 store
的 mutations
,又需要使用组件的局部方法,可以将它们结合起来:
<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 store
的 mutations
方法,也有组件自己的 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
模块中的 increment
和 decrement mutations
被映射为组件的方法。
mapMutations
是 Vuex
提供的一个非常方便的工具,可以简化在组件中调用 store mutations
的过程。通过字符串数组和对象的两种形式,你可以灵活地映射 store
的 mutations
到组件的方法中,并在需要时结合局部方法和模块化的 store
使用。这样,你的代码将更加清晰、简洁,并且易于维护。