Vuex Getter 完全指南:基于 Vuex 4 的 store 派生状态计算、两种访问方式与 mapGetters 映射实战
2026/9/19 22:34:43 网站建设 项目流程
  • 前端

【免费下载链接】vuex

🗃️ Centralized State Management for Vue.js.

项目地址:https://gitcode.com/gh_mirrors/vu/vuex
点击查看免费下载

Vuex 的 Getter 是定义在 store 内部的"计算属性",用于基于 state 派生过滤、统计、排序等计算结果,并在多个组件间共享复用。本文以 docs/ja/guide/getters.md(英文原版见 docs/guide/getters.md)为主线,结合 Vuex 4 源码(src/store-util.js、src/helpers.js)与仓库示例,完整讲解 Getter 的定义、属性式访问、方法式访问、mapGetters映射,以及其背后的缓存与响应式机制,读完即可在真实项目中正确、高效地使用 Getter。

为什么需要 Getter:从组件内联计算到 store 级派生状态

在组件中,我们常常需要基于 store 状态做派生计算,例如过滤待办列表并统计已完成数量。最直接的做法是在组件的computed中写:

computed: { doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length } }

问题在于:如果多个组件都需要这份派生逻辑,要么把函数复制多份(重复代码),要么抽成共享 helper 再在多个地方 import(依然不够内聚)。两种方式都不理想。

Vuex 的解决方案是在 store 中定义getter,把它看作"store 的 computed 属性":逻辑只写一次,任何组件都能通过store.getters访问同一份计算结果。

定义 Getter:接收 state 作为第一个参数

在创建 store 时,通过getters选项定义:

import { createStore } from 'vuex' const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos (state) { return state.todos.filter(todo => todo.done) } } })

Getter 的第一个参数永远是当前模块的state。定义好之后,store.getters.doneTodos即可得到[{ id: 1, text: '...', done: true }]

从源码看,getter 的实际注册发生在模块安装阶段:installModule遍历模块的 getters,通过registerGetter将原始 getter 包装进store._wrappedGetters(见 src/store-util.js 与 src/store-util.js)。包装函数会为 getter 依次传入四个参数:

store._wrappedGetters[type] = function wrappedGetter (store) { return rawGetter( local.state, // 本地(当前模块)state local.getters, // 本地 getters store.state, // 根 state store.getters // 根 getters ) }

也就是说,除了文档明确说明的前两个参数stategetters,getter 实际还能接收第 3、4 个参数:根模块的state与根模块的getters,这在模块化 store 中非常有用。

属性式访问(Property-Style Access):支持 getter 级联与响应式缓存

在 getter 中调用其他 getter

Getter 会接收其他 getter作为第二个参数,从而支持级联派生:

getters: { // ... doneTodosCount (state, getters) { return getters.doneTodos.length } }

访问store.getters.doneTodosCount得到1。第二个参数getters中可用的"其他 getter"在不同场景下含义不同:根模块中即全部根 getter;在模块内部(见下文"模块与命名空间"),则指本地 getters 代理对象(由makeLocalGetters创建,见 src/store-util.js)。

在组件中使用

任何组件内部都可以直接通过this.$store.getters使用:

computed: { doneTodosCount () { return this.$store.getters.doneTodosCount } }

属性式访问的缓存机制

以属性形式访问的 getter 会作为 Vue 响应式系统的一部分被缓存:只要依赖的 state 未变化,重复访问返回同一结果,不会重复执行过滤计算。

这一机制的底层实现位于 src/store-util.js 的resetStoreState

scope.run(() => { forEachValue(wrappedGetters, (fn, key) => { computedObj[key] = partial(fn, store) computedCache[key] = computed(() => computedObj[key]()) Object.defineProperty(store.getters, key, { get: () => computedCache[key].value, enumerable: true }) }) })

可见store.getters上的每个 getter 实际是一个computed(() => ...)对象(Vue 3 的computed),通过Object.defineProperty的 getter 暴露其.value。这些 computed 被包裹在一个独立创建的effectScope中(src/store-util.js),目的是让 getter 的响应式依赖不会因组件卸载而被销毁——这是 Vuex 4 为适配 Vue 3 组合式 API 做的关键设计。

注意事项:Vue 3.0 下的已知缓存问题

原文档特别给出警告:在 Vue 3.0 中,getter 的结果不像 computed 那样被缓存,这是当时的一个已知问题,需等待 Vue 3.2 修复(对应上游 PR 讨论)。也就是说,以属性式访问的 getter 缓存行为取决于你所用的 Vue 版本:在 Vue 3.2 及以后版本中可稳定依赖其缓存语义;若仍在使用 Vue 3.0,则应意识到 getter 可能被重复求值,避免在 getter 中放入昂贵或带副作用的计算。

方法式访问(Method-Style Access):通过返回函数向 getter 传参

当需要根据参数查询 store 中的数据(例如按 id 查找数组元素)时,可以让 getter 返回一个函数:

getters: { // ... getTodoById: (state) => (id) => { return state.todos.find(todo => todo.id === id) } }

调用方式变为函数调用:

store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

关键差异:通过方法访问的 getter 每次调用都会重新执行,结果不会被缓存。因此:

  • 适合"按需查询、参数化取值"的场景(如按 id 查找、按关键词过滤);
  • 不适合把昂贵计算放在内部——每次调用都会重新计算,无法利用响应式缓存;
  • 它本身是纯函数,不会自动追踪依赖,也不会自动响应 state 变化,需要配合组件内的computed或手动重新求值才能获得响应式。

对比可见:属性式访问 = 缓存 + 响应式;方法式访问 = 参数化 + 每次重算,二者按需取舍。

mapGetters辅助函数:把 getter 映射为本地 computed

mapGetters是 Vuex 提供的内置辅助函数,作用是把 store 的 getter映射为组件本地的 computed 属性,避免在模板中反复书写$store.getters.xxx

数组形式(同名映射)

import { mapGetters } from 'vuex' export default { // ... computed: { // 使用对象展开运算符把 getter 混入 computed ...mapGetters([ 'doneTodosCount', 'anotherGetter' // ... ]) } }

数组中的每个字符串既作为 store 中的 getter 名,也作为组件本地 computed 名。

对象形式(重命名映射)

如果希望映射为不同名称,使用对象形式:

...mapGetters({ // 将 `this.doneCount` 映射到 `this.$store.getters.doneTodosCount` doneCount: 'doneTodosCount' })

mapGetters 的源码实现

mapGetters定义于 src/helpers.js,其核心逻辑是:对每个映射项,生成一个名为mappedGetter的组件方法,方法内部返回this.$store.getters[val]

export const mapGetters = normalizeNamespace((namespace, getters) => { const res = {} if (__DEV__ && !isValidMap(getters)) { console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object') } normalizeMap(getters).forEach(({ key, val }) => { // 命名空间已被 normalizeNamespace 归一化(自动补上结尾的 '/') val = namespace + val res[key] = function mappedGetter () { if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) { return } if (__DEV__ && !(val in this.$store.getters)) { console.error(`[vuex] unknown getter: ${val}`) return } return this.$store.getters[val] } // 为 devtools 标记 vuex getter res[key].vuex = true }) return res })

值得注意的实现细节:

  • 归一化命名空间normalizeNamespace(src/helpers.js)会在 namespace 字符串不以/结尾时自动补/,所以mapGetters('foo', ...)mapGetters('foo/', ...)等价;
  • 开发态校验:当映射参数既不是数组也不是对象、或目标 getter 不存在时,会在开发环境打印console.error(对应测试 test/unit/helpers.spec.js 验证了"参数非法"与"getter 未定义"的错误提示);
  • 命名空间校验:使用命名空间时,若store._modulesNamespaceMap[namespace]中找不到对应模块,会打印[vuex] module namespace not found in mapGetters(): ...并静默返回;
  • 映射出的方法带有vuex = true标记,供 Vue Devtools 识别。

命名空间下的 mapGetters

当 store 采用带命名空间的模块时,可以把命名空间作为mapGetters的第一个参数:

computed: { ...mapGetters('foo', { a: 'hasAny', b: 'negative' }) }

对应测试 test/unit/helpers.spec.js 验证了:在namespaced: truefoo模块中,mapGetters('foo', { a: 'hasAny' })实际读取的是store.getters['foo/hasAny'],并会随store.commit('foo/inc')响应式更新。多层嵌套命名空间同理,可写作mapGetters('foo/bar', ...)(见 test/unit/helpers.spec.js)。

模块与命名空间中的 Getter:local state / local getters 的语义

当 getter 定义在模块内时,"第一个参数是 state"这句话需要更精确的表述:传入的是该模块的 local state,而不是根 state。同理,第二个参数getters在该模块内部指向本地 getters 代理(由makeLocalGetters在首次访问时构建并缓存到store._makeLocalGettersCache,见 src/store-util.js),代理只暴露属于该命名空间下的 getter。

来看仓库中的真实示例 examples/classic/chat/store/getters.js,它展示了 getter 的典型组合用法——级联、解构 state、引用其他 getter:

export const threads = state => state.threads export const currentThread = state => { return state.currentThreadID ? state.threads[state.currentThreadID] : {} } export const currentMessages = state => { const thread = currentThread(state) return thread.messages ? thread.messages.map(id => state.messages[id]) : [] } export const unreadCount = ({ threads }) => { return Object.keys(threads).reduce((count, id) => { return threads[id].lastMessage.isRead ? count : count + 1 }, 0) } export const sortedMessages = (state, getters) => { const messages = getters.currentMessages return messages.slice().sort((a, b) => a.timestamp - b.timestamp) }

其中currentMessages在 getter 内部直接调用另一个 getter 函数(currentThread(state))——这是函数式拆分 getter 的常用写法;而sortedMessages则通过第二个参数getters引用getters.currentMessages完成级联派生。组件侧则在 examples/classic/chat/components/MessageSection.vue 用mapGetterscurrentThreadsortedMessages映射为本地 computed:

computed: mapGetters({ thread: 'currentThread', messages: 'sortedMessages' })

而在 examples/classic/shopping-cart/store/modules/products.js 中可以看到模块化 store 的完整形态(namespaced: true+state/getters/actions/mutations拆分),命名空间模块下的 getter 需以模块名/getter名形式访问。

Composition API 下如何使用 Getter

Vuex 4 全面支持 Vue 3 组合式 API。在<script setup>setup()中,可以使用useStore()获取 store 实例(配合 src/injectKey.js 中导出的注入 key),再以与选项式 API 相同的两条路径访问 getter:

import { useStore } from 'vuex' import { computed } from 'vue' const store = useStore() // 属性式访问:配合 computed 获得响应式 const doneTodosCount = computed(() => store.getters.doneTodosCount) // 方法式访问:参数化查询 const todoById = (id) => store.getters.getTodoById(id)

注意两点:

  • store.getters本身是响应式的(底层是 computed 集合),但解构取值(如const { doneTodosCount } = store.getters)会丢失响应性,务必通过computed(() => store.getters.xxx)包裹;
  • 若使用命名空间模块,可借助createNamespacedHelpers(src/helpers.js)生成预绑定命名空间的mapGetters等辅助函数,减少重复书写命名空间前缀。

更多组合式写法可参考仓库 docs/guide/composition-api.md 与 examples/composition 目录下的示例。

实战要点速查

场景推荐写法缓存行为
过滤/统计/排序等纯派生计算属性式访问 + computed响应式缓存(Vue 3.2+)
按 id、关键词等参数查询方法式访问(getter 返回函数)每次调用重新执行
组件内省去$store.getters前缀mapGetters([...])/mapGetters({ 别名: '原名' })与属性式访问一致
命名空间模块store.getters['foo/bar']mapGetters('foo/bar', ...)与属性式访问一致
Composition APIcomputed(() => store.getters.x)与属性式访问一致

最后给出几条实践中最重要的原则:

  1. getter 必须是纯函数——只基于state/getters参数计算,不要在 getter 内直接修改 state(修改 state 只能通过 mutation,参见 docs/guide/mutations.md);
  2. 优先属性式访问,让 Vue 的 computed 缓存帮你避免重复计算;只有需要参数化查询时才用方法式访问,并注意其不缓存的开销;
  3. getter 可以被其他 getter 组合,善用(state, getters)第二个参数,把复杂派生拆成小而可复用的 getter 链;
  4. 多组件共享的派生逻辑都应下沉到 store 的 getter 中,这正是 Vuex"集中式状态管理"的核心价值之一。

关于 getter 在模块化 store、热重载(hot reload,getter 会在 src/store-util.js 的resetStore流程中被重建)与严格模式下的行为,可继续阅读仓库的 docs/guide/modules.md 与 docs/guide/strict.md。

  • 前端

【免费下载链接】vuex

🗃️ Centralized State Management for Vue.js.

项目地址:https://gitcode.com/gh_mirrors/vu/vuex
点击查看免费下载
上一篇:OpenCore Legacy Patcher终极指南:五步让你的老Mac焕发新生
下一篇:ComfyUI-Workflows-ZHO:AI创作终极指南与完整中文工作流集合

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询