返回 导航

Vue.js

hangge.com

Vue.js - 使用Vuex进行状态管理详解4(mutations同步修改状态)

作者:hangge | 2026-10-03 09:09
    在前面的文章中,我介绍了 Vuex 中 state 状态的定义与读取,以及 getters 计算属性的使用。但我们还没有涉及如何修改 store 中的状态。在 Vuex 中,修改状态的唯一方式是提交 mutation。mutations 类似于事件注册,每个 mutation 都有一个字符串类型和一个回调函数,回调函数就是实际进行状态更改的地方。本文将详细介绍 mutations 的基本使用、参数传递、常量类型定义以及 mapMutations 辅助函数的使用技巧。

一、基本介绍

1,什么是 mutations

(1)更改 Vuex 的 store 中状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)。

(2)这个回调函数就是我们实际进行状态更改的地方,并且它会接收 state 作为第一个参数。
注意:我们不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:当触发一个类型为 increment 的 mutation 时,调用此函数。要唤醒一个 mutation 处理函数,我们需要以相应的 type 调用 store.commit 方法。

2,mutations 必须是同步函数

(1)一条重要的原则:在 Vuex 中,mutations 必须是同步函数。因为 Vue.js devtools 工具会记录 mutations 函数的日志,每条 mutation 被记录时,devtools 都需要捕捉到前一次状态和后一次状态的快照。

(2)如果在 mutation 中执行异步操作,就无法追踪到数据的变化,这与 mutation 的设计初心相悖。
提示:如果需要执行异步操作来修改状态,应该使用 actions(将在下一篇文章中介绍)。actions 可以包含任意异步操作,然后通过提交 mutation 来修改状态。

二、mutations 基本使用

1,定义 mutations

    下面通过一个计数器案例来演示 mutations 的基本使用。修改 src/store/index.js 文件,在 mutations 中定义了两个函数:increment 和 decrement。这两个函数不能直接调用,因为 mutations 中定义的选项更像事件注册。
import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      counter: 0
    }
  },
  // 定义 mutations
  mutations: {
    // 定义 increment 函数,可获取 state 对象
    increment(state) {
      state.counter++
    },
    // 定义 decrement 函数
    decrement(state) {
      state.counter--
    }
  }
})

export default store

2,提交 mutation

(1)要调用 mutations 中定义的函数,需要使用 $store.commit 方法提交一个类型。下面是我们组件代码:
  • 可以看到,当单击 "+1" 按钮时,会调用 $store.commit('increment') 方法提交一个类型为 increment 的 mutation 函数,即上面 mutations 中定义的 increment 函数。
<template>
  <div>
    <h4>当前计数:{{ $store.state.counter }}</h4>
    <button @click="$store.commit('increment')">+1</button>
    <button @click="$store.commit('decrement')">-1</button>
  </div>
</template>

<script>
export default {}
</script>
(2)保存代码后,在浏览器中显示的效果如下,计数器可以正常使用:

三、mutations 接收参数

1,payload 参数说明

(1)在 mutations 中定义的函数可以接收两个参数:
  • 第一个参数:state 对象,可以访问和修改 store 中定义的状态
  • 第二个参数:payload(载荷),用于接收提交时传递过来的参数
提示:在大多数情况下,payload 应该是一个对象,这样可以包含多个字段,并且记录的 mutation 会更易读。

2,使用示例

(1)继续完善上面的计数器案例,添加一个 "+10" 的功能。修改 store/index.js 文件:
import { createStore } from 'vuex'

const store = createStore({
  state() {
    return {
      counter: 0,
    }
  },
  mutations: {
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    },
    // 定义 incrementN 函数,接收 payload 参数
    incrementN(state, payload) {
      // payload 通常是对象类型
      state.counter += payload.num
    },
  },
})

export default store
(2)接着修改组件,添加一个 "+10" 按钮:
<template>
  <div>
    <h4>当前计数:{{ $store.state.counter }}</h4>
    <button @click="$store.commit('increment')">+1</button>
    <button @click="$store.commit('decrement')">-1</button>
    <button @click="addTen">+10</button>
  </div>
</template>

<script>
export default {
  methods: {
    addTen() {
      // 向 mutation 的 payload 参数传递一个对象
      this.$store.commit('incrementN', { num: 10, name: 'hangge', age: 18 })
    }
  }
}
</script>
(3)保存代码后,在浏览器中显示的效果如下,"+10" 功能可以正常使用:

3,对象风格的提交方式

$store.commit 还支持对象风格的提交方式,将 type 和其他参数放在同一个对象中。
提示:当使用对象风格的提交方式时,整个对象都作为载荷传给 mutation 函数。
// 方式一:普通的提交方式
this.$store.commit('incrementN', { num: 10, name: 'hangge', age: 18 })

// 方式二:对象风格的提交方式
this.$store.commit({
  type: 'incrementN',  // 指定 mutation 类型
  num: 10,
  name: 'hangge',
  age: 18
})

四、mutations 常量类型

1,为什么需要常量类型

(1)从上面的案例中可以看出,为了触发 mutations 中的回调函数,$store.commit 提交的 type 值必须与 mutations 中定义的函数名称相同。

(2)为了确保 type 值与函数名称一一对应,我们通常会将 type 值提取为一个常量,这样可以避免在工作中出现不必要的错误。
提示:使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然。

2,使用示例

(1)新建 src/store/mutation-types.js 文件,定义常量:
export const INCREMENT_N = 'incrementN'

(2)修改 store/index.js 文件,使用常量作为函数名:
import { createStore } from 'vuex'
import { INCREMENT_N } from './mutation-types'

const store = createStore({
  state() {
    return {
      counter: 0
    }
  },
  mutations: {
    increment(state) {
      state.counter++
    },
    decrement(state) {
      state.counter--
    },
    // 使用常量作为函数名(ES2015 计算属性名)
    [INCREMENT_N](state, payload) {
      state.counter += payload.num
    }
  }
})

export default store

(3)然后在组件中使用这个常量:
<template>
  <div>
    <h4>当前计数:{{ $store.state.counter }}</h4>
    <button @click="$store.commit('increment')">+1</button>
    <button @click="$store.commit('decrement')">-1</button>
    <button @click="addTen">+10</button>
  </div>
</template>

<script>
import { INCREMENT_N } from './store/mutation-types'

export default {
  methods: {
    addTen() {
      // 使用常量代替字符串
      this.$store.commit(INCREMENT_N, { num: 10, name: 'hangge', age: 18 })
    }
  }
}
</script>

(4)保存代码后,在浏览器中运行,计数器的 "+10" 功能可以正常使用。

五、mapMutations 辅助函数

1,在 methods 中使用

(1)在 Vuex 中,不仅提供 mapState 函数来简化状态获取,还提供 mapMutations 辅助函数用于简化触发 mutation 函数的操作。

(2)mapMutations 函数的语法与 mapState 函数类似,支持数组和对象两种语法形式:
<template>
  <div>
    <h4>当前计数:{{ $store.state.counter }}</h4>
    <!-- 直接使用映射好的函数 -->
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="incrementN({ num: 20 })">+20</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'
import { INCREMENT_N } from './store/mutation-types'

export default {
  methods: {
    // 数组语法:将 this.increment() 映射为 this.$store.commit('increment')
    ...mapMutations(['increment']),
    
    // 对象语法:将 this.incrementN() 映射为 this.$store.commit(INCREMENT_N)
    ...mapMutations({
      incrementN: INCREMENT_N
    })
  }
}
</script>

(3)可以看到,mapMutations 函数返回的对象中包含了对应映射好的函数,当我们为 methods 解构返回值时,在模板上就可以直接使用这些映射好的函数。


2,在 setup 中使用

(1)在 setup 函数中使用 mapMutations 时,可以直接解构返回,不需要像 mapState 那样进行额外封装:
注意:mapMutations 和 mapActions 可以在 setup 中直接展开使用,因为它们返回的是函数而不是计算属性。而 mapState 和 mapGetters 返回的是需要响应式的计算属性,需要额外封装。
<template>
  <div>
    <h4>当前计数:{{ $store.state.counter }}</h4>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="incrementN({ num: 20 })">+20</button>
  </div>
</template>

<script>
import { mapMutations } from 'vuex'

export default {
  setup() {
    // 在 setup 中直接解构使用
    const storeMutations = mapMutations(['decrement'])
    
    return {
      ...storeMutations
    }
  },
  methods: {
    ...mapMutations(['increment']),
    ...mapMutations({
      incrementN: 'incrementN'
    })
  }
}
</script>

(2)可以看到,mapMutations 在 methods 和 setup 中都可以使用,这两种方式只是语法不同,效果是一样的。

评论

全部评论(0)

回到顶部