在Vue.js的状态管理中,Vuex是一个非常重要的工具,它帮助开发者集中管理应用的状态。Vuex的核心概念包括state、mutations、actions、getters和modules。今天,我们要深入探讨其中一个关键部分:getters,以及它的相关辅助函数mapGetters。通过详细介绍getters的原理和实现过程,希望能帮助你更好地理解和使用它们。
什么是Vue Getters?
Vuex中的getters可以被视为store的计算属性。就像Vue组件中的计算属性一样,getters的返回值会基于其依赖被缓存起来,且只有当它的依赖值发生变化时才会重新计算。这使得getters非常适合用于从store中的state派生出一些状态。
基本使用
首先,让我们看一个简单的例子:
代码语言:javascript复制const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Learn Vuex', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
在上面的代码中,我们定义了一个doneTodos
的getter,它会返回所有已完成的任务。同时,我们还定义了一个doneTodosCount
的getter,它依赖于doneTodos
,返回已完成任务的数量。
访问Getters
你可以通过store.getters
来访问getters:
store.getters.doneTodos // -> [{ id: 1, text: 'Learn Vue', done: true }]
store.getters.doneTodosCount // -> 1
在组件中使用Getters
在Vue组件中,你可以使用this.$store.getters
来访问getters:
computed: {
doneTodos () {
return this.$store.getters.doneTodos
}
}
这虽然工作正常,但对于多个getters的访问会显得有些冗长。为了解决这个问题,我们可以使用mapGetters
辅助函数。
使用mapGetters
mapGetters
是一个辅助函数,它可以帮助我们将store中的getter映射到局部计算属性。它可以极大地简化在组件中使用getters的代码量。
基本使用
首先,我们需要在组件中导入mapGetters
:
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'doneTodos',
'doneTodosCount'
])
}
}
现在,我们可以直接在模板中使用这些计算属性:
代码语言:html复制<template>
<div>
<p>Done Todos: {{ doneTodos }}</p>
<p>Done Todos Count: {{ doneTodosCount }}</p>
</div>
</template>
别名
有时候,我们可能想要为映射的计算属性指定别名。这时可以使用对象形式的参数:
代码语言:javascript复制computed: {
...mapGetters({
completedTasks: 'doneTodos',
completedTasksCount: 'doneTodosCount'
})
}
这样,我们就可以在模板中使用别名:
代码语言:html复制<template>
<div>
<p>Completed Tasks: {{ completedTasks }}</p>
<p>Completed Tasks Count: {{ completedTasksCount }}</p>
</div>
</template>
Getters的原理和实现
为了更深入地理解getters的工作原理,我们需要了解Vuex的内部实现。Vuex是基于Vue的响应系统构建的,因此getters的实现与Vue的计算属性有很多相似之处。
创建Getters
当我们创建一个store时,Vuex会遍历我们定义的所有getters,并为每一个getter创建一个计算属性。这些计算属性的结果会被缓存,只有当它们的依赖(即state或者其他getters)发生变化时才会重新计算。
代码语言:javascript复制class Store {
constructor (options = {}) {
// ...
const store = this
const { getters } = options
this.getters = {}
Object.keys(getters).forEach(key => {
const fn = getters[key]
Object.defineProperty(store.getters, key, {
get: () => fn(store.state, store.getters)
})
})
// ...
}
}
在上面的代码中,我们可以看到Vuex通过Object.defineProperty
为每一个getter定义了一个属性,这个属性的getter函数会返回计算后的结果。
响应式系统
Vuex的state是响应式的,这意味着当我们改变state中的数据时,所有依赖于这些数据的getters都会自动更新。Vuex通过Vue的Vue.observable
方法将state变成响应式对象。
const state = Vue.observable({
todos: [
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Learn Vuex', done: false }
]
})
这样,当我们改变state中的todos时,所有依赖于todos的getters(例如doneTodos
和doneTodosCount
)都会自动重新计算,并触发相关的视图更新。
深入理解mapGetters
mapGetters
是Vuex提供的一个非常有用的辅助函数,它的实现也相对简单。mapGetters
的主要作用是将store中的getters映射到组件的计算属性。
mapGetters的实现
我们来看看mapGetters
的实现:
export function mapGetters (getters) {
const res = {}
normalizeMap(getters).forEach(({ key, val }) => {
res[key] = function mappedGetter () {
return this.$store.getters[val]
}
})
return res
}
在上面的代码中,mapGetters
首先通过normalizeMap
函数将传入的参数规范化为一个数组,然后遍历这个数组,为每一个getter创建一个计算属性。这些计算属性的getter函数会返回this.$store.getters
中的对应值。
使用normalizeMap
normalizeMap
函数的作用是将传入的参数(可以是数组或对象)规范化为一个标准的对象数组:
function normalizeMap (map) {
if (!isValidMap(map)) {
return []
}
return Array.isArray(map)
? map.map(key => ({ key, val: key }))
: Object.keys(map).map(key => ({ key, val: map[key] }))
}
如果传入的是一个数组,normalizeMap
会将每一个数组元素转化为一个对象,键和值相同;如果传入的是一个对象,normalizeMap
会将每一个键值对转化为一个对象,键和值分别对应原对象的键和值。
Getters和mapGetters的实际应用
在实际项目中,getters和mapGetters可以帮助我们更好地组织和管理应用状态。让我们通过一个稍微复杂的例子来进一步理解它们的实际应用。
例子:Todo应用
假设我们在开发一个Todo应用,这个应用需要展示所有任务、已完成任务、未完成任务以及任务的数量。我们可以通过getters来实现这些功能。
首先,我们定义store的state和getters:
代码语言:javascript复制const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Learn Vuex', done: false },
{ id: 3, text: 'Build something awesome', done: false }
]
},
getters: {
allTodos: state => state.todos,
doneTodos: state => state.todos.filter(todo => todo.done),
undoneTodos: state => state.todos.filter(todo => !todo.done),
totalTodosCount: state => state.todos.length,
doneTodosCount: (state, getters) => getters.doneTodos.length,
undoneTodosCount: (state, getters) => getters.undoneTodos.length
}
})
然后,在组件中使用这些getters:
代码语言:javascript复制import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters([
'allTodos',
'doneTodos',
'undoneTodos',
'totalTodosCount',
'doneTodosCount',
'undoneTodosCount'
])
}
}
在模板中展示任务和统计信息:
代码语言:html复制<template>
<div>
<h1>Todo List</h1>
<p>Total Todos: {{ totalTodosCount }}</p>
<p>Done Todos: {{ doneTodosCount }}</p>
<p>Undone Todos: {{ undoneTodosCount }}</p>
<h2>All Todos</h2>
<ul>
<li v-for="todo in allTodos" :key="todo.id">{{ todo.text }}</li>
</ul>
<h2>Done Todos</h2>
<ul>
<li v-for="todo in doneTodos" :key="todo.id">{{ todo.text }}</li>
</ul>
<h2>Undone Todos</h2>
<ul>
<li v-for="todo in undoneTodos" :key="todo.id">{{ todo.text }}</li>
</ul>
</div>
</template>
通过这种方式,我们可以清晰地展示所有任务、已完成任务和未完成任务,以及相关的统计信息。而且,这些数据都是通过getters从state派生出来的,当state中的任务列表发生变化时,视图会自动更新。
优化和最佳实践
在实际开发中,除了正确使用getters和mapGetters,我们还可以采取一些优化和最佳实践来提升代码的可维护性和性能。
避免不必要的计算
虽然getters的结果会被缓存,但在设计getters时仍然要注意避免不必要的计算。例如,如果一个getter依赖于另一个getter,我们应该尽量减少重复计算。
模块化
对于大型应用,我们可以将store拆分成多个模块,每个模块都有自己的state、mutations、actions和getters。这样可以使代码更清晰,更易于管理。
代码语言:javascript复制const moduleA = {
state: () => ({
todos: []
}),
getters: {
doneTodos: state => state.todos.filter(todo => todo.done)
},
mutations: {
// ...
},
actions: {
// ...
}
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
在组件中使用模块的getters:
代码语言:javascript复制computed: {
...mapGetters('a', [
'doneTodos'
])
}
异步操作
虽然getters不应该包含异步操作,但我们可以在actions中进行异步操作,然后通过mutations更新state,从而触发getters的重新计算。
代码语言:javascript复制const store = new Vuex.Store({
state: {
todos: []
},
getters: {
doneTodos: state => state.todos.filter(todo => todo.done)
},
mutations: {
setTodos (state, todos) {
state.todos = todos
}
},
actions: {
fetchTodos ({ commit }) {
// 假设我们有一个API调用来获取todos
fetchTodosFromAPI().then(todos => {
commit('setTodos', todos)
})
}
}
})
性能优化
在高性能需求的应用中,我们可以利用Vuex的插件系统来优化getters的性能。例如,我们可以编写一个插件来对getters的结果进行缓存,从而避免频繁的计算。
代码语言:javascript复制function createGettersCachePlugin () {
return store => {
const cache = {}
store.subscribe((mutation, state) => {
// 在每次mutation后清除缓存
Object.keys(cache).forEach(key => delete cache[key])
})
store._wrappedGetters = Object.keys(store._wrappedGetters).reduce((wrappedGetters, key) => {
const getter = store._wrappedGetters[key]
wrappedGetters[key] = (state, getters) => {
if (!cache[key]) {
cache[key] = getter(state, getters)
}
return cache[key]
}
return wrappedGetters
}, {})
}
}
const store = new Vuex.Store({
// ...
plugins: [createGettersCachePlugin()]
})
这个插件在每次mutation后清除缓存,并对getters的结果进行缓存,从而减少不必要的计算。
总结
Vuex的getters和mapGetters是非常强大的工具,它们可以帮助我们从store中的state派生出新的状态,并在组件中方便地使用这些状态。在实际开发中,我们可以通过合理使用getters和mapGetters,提高代码的可维护性和性能。同时,我们还可以采用一些优化和最佳实践,使我们的应用更加健壮和高效。
希望通过本文的详细介绍,你能够对Vuex的getters和mapGetters有更深入的理解,并在实际项目中更好地应用它们。祝你在Vue.js的世界中编程愉快!
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!