好的,我们现在开始。
Vuex 中的 Getters 与 Actions:状态管理的利器
大家好,今天我们要深入探讨 Vuex 中两个非常重要的概念:Getters 和 Actions。它们是 Vuex 状态管理模式中不可或缺的组成部分,合理运用它们能够极大地提升代码的可维护性、可测试性和可读性。
Getters:从 Store 中派生状态
什么是 Getters?
Getters 可以理解为 Vuex store 的计算属性。它们用于从 store 中的 state 中派生出新的状态。类似于组件中的 computed 属性,Getters 会根据依赖的 state 变化而自动更新。
为什么要用 Getters?
直接访问 state 虽然简单,但在复杂应用中,我们经常需要对 state 进行一些处理才能得到我们想要的数据。如果每次都在组件中进行相同的处理,代码就会变得冗余且难以维护。Getters 的出现就是为了解决这个问题。
- 代码复用: 相同的状态计算逻辑可以被多个组件复用。
- 数据封装: Getters 可以隐藏 state 的内部结构,向组件暴露更简洁的数据接口。
- 性能优化: Getters 会缓存计算结果,只有当依赖的 state 发生变化时才会重新计算。
如何定义 Getters?
Getters 是一个对象,其属性是函数。这些函数接收 state 作为第一个参数,还可以接收其他的 getters 作为第二个参数。
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Learn Vuex', done: true },
{ id: 2, text: 'Learn Getters', done: false },
{ id: 3, text: 'Learn Actions', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
},
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
},
mutations: {
// ... mutations
},
actions: {
// ... actions
}
})
export default store
在这个例子中:
doneTodos
:返回所有已完成的 todo 列表。doneTodosCount
:返回已完成的 todo 数量。它使用了doneTodos
这个 getter。getTodoById
:返回指定 ID 的 todo。这是一个带有参数的 getter。
如何使用 Getters?
在组件中,我们可以使用 mapGetters
辅助函数将 getters 映射到组件的 computed 属性中。
// MyComponent.vue
<template>
<div>
<p>已完成的 Todo 数量:{{ doneTodosCount }}</p>
<ul>
<li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li>
</ul>
<p>Todo ID为2的内容: {{todo2.text}}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodos',
'doneTodosCount'
]),
todo2(){
return this.$store.getters.getTodoById(2)
}
}
}
</script>
或者,我们也可以直接通过 store.getters
访问 getters。
// MyComponent.vue
<template>
<div>
<p>已完成的 Todo 数量:{{ doneTodosCount }}</p>
</div>
</template>
<script>
export default {
computed: {
doneTodosCount() {
return this.$store.getters.doneTodosCount
}
}
}
</script>
Getters 的参数
Getters 可以接受参数,这使得我们可以创建动态的 getters。在上面的例子中,getTodoById
就是一个带参数的 getter。 要使用带参数的 getter,我们需要返回一个函数。
Actions:提交 Mutations 的方式
什么是 Actions?
Actions 类似于 mutations,但是 actions 用于处理异步操作。Actions 提交的是 mutations,而不是直接变更状态。
为什么要用 Actions?
Vuex 规定,mutation 必须是同步函数。这是因为 Vuex 需要在 mutation 完成后知道状态何时发生了变化,以便能够正确地更新视图。如果 mutation 中包含异步操作,Vuex 无法追踪状态的变化。
Actions 的出现就是为了处理异步操作。在 action 中,我们可以进行 API 调用、定时器操作等异步操作,然后在异步操作完成后,提交 mutation 来变更状态。
- 处理异步操作: 允许在 Vuex 中处理异步任务,例如 API 调用。
- 解耦: 将异步逻辑从组件中分离出来,使组件更加专注于视图渲染。
- 可测试性: Actions 易于测试,因为它们可以模拟异步操作的结果。
如何定义 Actions?
Actions 是一个对象,其属性是函数。这些函数接收一个 context 对象作为第一个参数。Context 对象包含以下属性:
state
:store 的 state。getters
:store 的 getters。commit
:提交 mutation 的方法。dispatch
:触发其他 action 的方法。rootState
:在模块化 store 中,访问根 state。rootGetters
:在模块化 store 中,访问根 getters。
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0,
isLoading: false
},
getters: {
// ... getters
},
mutations: {
increment (state) {
state.count++
},
setLoading (state, isLoading) {
state.isLoading = isLoading
}
},
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
fetchData ({ commit }) {
commit('setLoading', true)
// 模拟 API 调用
return new Promise((resolve) => {
setTimeout(() => {
commit('setLoading', false)
resolve()
}, 2000)
})
}
}
})
export default store
在这个例子中:
incrementAsync
:异步地增加count
。它使用了commit
方法提交increment
mutation。fetchData
:模拟从 API 获取数据。它首先使用commit
方法设置isLoading
为true
,然后在异步操作完成后,设置isLoading
为false
。
如何使用 Actions?
在组件中,我们可以使用 mapActions
辅助函数将 actions 映射到组件的 methods 中。
// MyComponent.vue
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="incrementAsync">异步增加</button>
<button @click="fetchData">获取数据</button>
<p v-if="isLoading">Loading...</p>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
computed: {
count() {
return this.$store.state.count
},
isLoading() {
return this.$store.state.isLoading
}
},
methods: {
...mapActions([
'incrementAsync',
'fetchData'
]),
// 或者
// ...mapActions({
// increase: 'incrementAsync', // 将 `this.increase()` 映射为 `this.$store.dispatch('incrementAsync')`
// getData: 'fetchData'
// })
}
}
</script>
或者,我们也可以直接通过 store.dispatch
触发 action。
// MyComponent.vue
<template>
<div>
<button @click="incrementAsync">异步增加</button>
</div>
</template>
<script>
export default {
methods: {
incrementAsync() {
this.$store.dispatch('incrementAsync')
}
}
}
</script>
Actions 的返回值
Actions 可以返回 Promise 对象。这使得我们可以方便地处理异步操作的结果。在上面的例子中,fetchData
action 返回了一个 Promise 对象。
Actions 的参数
Actions 可以接受参数。这些参数可以来自组件,也可以是其他 action 触发时传递的。
// store.js
// ...
mutations: {
addTodo (state, todo) {
state.todos.push(todo)
}
},
actions: {
addTodoAsync ({ commit }, text) {
// 模拟 API 调用
setTimeout(() => {
const todo = {
id: Math.random(),
text: text,
done: false
}
commit('addTodo', todo)
}, 500)
}
}
// ...
// MyComponent.vue
// ...
methods: {
addTodo() {
this.$store.dispatch('addTodoAsync', this.newTodoText)
this.newTodoText = ''
}
}
// ...
Getters 和 Actions 的最佳实践
- 保持 Getters 纯粹: Getters 应该只用于从 state 中派生状态,不应该有任何副作用。
- Actions 只负责提交 Mutations: Actions 应该只负责处理异步操作,然后提交 mutation 来变更状态。不要在 actions 中直接修改 state。
- 合理使用
mapGetters
和mapActions
: 这两个辅助函数可以简化代码,提高可读性。 - Actions 返回 Promise: 方便处理异步操作的结果。
- 命名规范:
- Getters: 使用动词或形容词来描述 getter 的作用,例如
getDoneTodos
、isLoggedIn
。 - Actions: 使用动词来描述 action 的操作,例如
fetchData
、updateUser
。
- Getters: 使用动词或形容词来描述 getter 的作用,例如
- 模块化 Store: 当应用变得复杂时,将 store 分成多个模块可以提高代码的可维护性。
Getters vs Computed Properties
特性 | Getters | Computed Properties |
---|---|---|
作用 | 从 store 的 state 中派生状态 | 基于组件的数据进行计算 |
数据来源 | Vuex store 的 state | 组件的 data 或 props |
复用性 | 多个组件可以复用相同的 getter | 仅限于当前组件 |
依赖追踪 | 自动追踪依赖的 state,当 state 变化时自动更新 | 自动追踪依赖的 data 或 props,当它们变化时自动更新 |
访问方式 | store.getters.getterName 或 mapGetters 辅助函数 |
this.computedPropertyName |
缓存 | 有缓存机制,只有当依赖的 state 变化时才会重新计算 | 有缓存机制,只有当依赖的 data 或 props 变化时才会重新计算 |
Actions vs Mutations
特性 | Actions | Mutations |
---|---|---|
作用 | 处理异步操作,提交 mutations | 直接修改 state |
同步/异步 | 异步 | 同步 |
触发方式 | store.dispatch('actionName') 或 mapActions |
store.commit('mutationName') |
接受参数 | 可以接受任意数量的参数 | 通常接受一个 state 和一个 payload (参数) |
返回值 | 可以返回 Promise | 没有返回值 |
日志记录 | 可以被 Vuex 的开发工具记录 | 可以被 Vuex 的开发工具记录 |
一个更复杂的例子:模块化 Store
当应用变得复杂时,将 store 分成多个模块可以提高代码的可维护性。
// store/modules/todos.js
const state = {
todos: []
}
const getters = {
allTodos: state => state.todos,
completedTodos: state => state.todos.filter(todo => todo.done)
}
const mutations = {
ADD_TODO (state, todo) {
state.todos.push(todo)
},
TOGGLE_TODO (state, id) {
const todo = state.todos.find(todo => todo.id === id)
if (todo) {
todo.done = !todo.done
}
}
}
const actions = {
addTodo ({ commit }, text) {
const todo = {
id: Math.random(),
text: text,
done: false
}
commit('ADD_TODO', todo)
},
toggleTodo ({ commit }, id) {
commit('TOGGLE_TODO', id)
}
}
export default {
namespaced: true, // 确保模块拥有更高的封装度。有了命名空间后,getter、action 和 mutation 都会自动根据模块注册的路径调整命名方式
state,
getters,
mutations,
actions
}
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import todos from './modules/todos'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
todos
}
})
export default store
// MyComponent.vue
<template>
<div>
<ul>
<li v-for="todo in allTodos" :key="todo.id">
<input type="checkbox" :checked="todo.done" @change="toggleTodo(todo.id)">
{{ todo.text }}
</li>
</ul>
<input type="text" v-model="newTodoText">
<button @click="addTodo">Add Todo</button>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
data() {
return {
newTodoText: ''
}
},
computed: {
...mapGetters('todos', [ // 使用模块的命名空间
'allTodos'
])
},
methods: {
...mapActions('todos', [ // 使用模块的命名空间
'addTodo',
'toggleTodo'
])
}
}
</script>
在这个例子中,我们将 todos 相关的状态、getters、mutations 和 actions 放在一个名为 todos
的模块中。 namespaced: true
选项确保模块拥有更高的封装度。
总结一下,Getters 允许我们从 store 中派生状态, Actions 允许我们处理异步操作并提交 mutations。 它们是 Vuex 状态管理中至关重要的工具,能够帮助我们构建可维护、可测试和可读性强的 Vue 应用。