Vue.js - 使用Vuex进行状态管理详解2(state全局数据源)
作者:hangge | 2026-09-30 09:59
在上一篇文章中,我介绍了 Vuex 的基本概念和安装配置。本文我们将深入探讨 Vuex 核心概念中的state(状态)。state 是 Vuex 存储数据的地方,类似于 Vue 组件中的 data 选项。通过 state,我们可以实现组件间的状态共享,避免繁琐的 props 传递和事件通信。本文将详细介绍 state 的概念、在组件中读取 state 的多种方式,以及如何封装 mapState 辅助函数。




一、基本介绍
1,什么是 state
(1)state 表示应用的状态,即存储数据的地方。Vuex 通过「单一状态树」来管理应用层级的全部状态,将应用的所有状态存储在一个单独的 store 中,而不是分散在多个 store 对象或各个组件的 data 属性中。
(2)这种设计模式可以方便地对数据进行全局状态的管理,避免组件间状态共享带来的复杂性,也更容易追踪状态的变化。同时,「单一状态树」也让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
(3)下面是一个简单的 store 定义示例,我们在 state 中定义了三个全局变量:
import { createStore } from 'vuex'
const store = createStore({
state() {
// 定义全局共享的状态
return {
counter: 0,
name: 'hangge',
age: 88
}
}
})
export default store
2,state 的响应式特性
(1)存储在 Vuex 中的数据和 Vue 实例中的 data 遵循相同的规则,状态对象必须是纯粹(plain)的对象。
(2)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
提示:使用 Vuex 并不意味着我们需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。
二、在 Options API 中读取 state
1,直接在模板中读取
(1)Vuex 通过 Vue 的插件系统将 store 实例从根组件中注入到所有的子组件里,子组件能通过 this.$store 访问到。
(2)最简单的读取方式是在模板中直接使用 $store.state 来访问状态数据:
提示:这种方式虽然简单,但当组件需要获取多个状态时,代码会变得冗长,且不利于维护。
<template>
<div>
<h4>Home: {{ $store.state.counter }}</h4>
</div>
</template>

2,使用计算属性读取状态
(1)由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。这样每当 store.state.counter 变化的时候,都会重新求取计算属性,并且触发更新相关联的 DOM。
提示:模板中的 $store.state.counter 表达式可以抽取到计算属性中编写,效果是一样的,但代码更加清晰易读。
<template>
<div>
<h4>Home: {{ $store.state.counter }}</h4>
<h4>Home: {{ counter }}</h4>
</div>
</template>
<script>
export default {
computed: {
counter() {
// 使用计算属性读取状态
return this.$store.state.counter
}
}
}
</script>
(2)运行结果如下:

3,使用 mapState 辅助函数
(1)当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。
(2)我们修改代码,首先在 vuex 中导入 mapState 函数。然后使用 mapState 函数获取 state 中的数据,向该函数传递一个对象作为参数。其中对象参数的三种写法:
- 箭头函数:counter: state => state.counter,箭头函数不绑定 this,只能访问 state 参数。
- 普通函数:name(state) { return this.firstName + state.name },普通函数会绑定 this,可以访问组件实例的数据。
- 字符串别名:hanggeAge: 'age',等同于 state => state.age,适用于简单的属性映射。
<template>
<div>
<h4>Home: {{ $store.state.counter }}</h4>
<h4>Home: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
<h4>Age: {{ hanggeAge }}</h4>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
firstName: 'coder'
}
},
computed: mapState({ // mapState 函数接收对象类型参数
counter: (state) => {
// 箭头函数不绑定 this
return state.counter
},
name(state) {
// 普通函数会绑定 this,这里的 this 是组件实例
return this.firstName + state.name
},
hanggeAge: 'age' // key 可自定义,这里自定义为 hanggeAge
})
}
</script>
(3)当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组,数组中的每个属性会自动映射到 state 中的同名属性,代码更加简洁。
提示:数组参数方式适用于映射名称与 state 属性名完全相同的场景。如果需要重命名或进行额外处理,应使用对象参数方式。
<template>
<div>
<h4>Home: {{ $store.state.counter }}</h4>
<h4>Home: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
// mapState 函数接收数组类型参数
computed: mapState(['counter', 'name'])
}
</script>

4,对象展开运算符
(1)mapState 函数返回的是一个对象。通过展开运算符 ...,我们可以在计算属性中同时包含自己的计算属性和从 state 映射过来的属性,实现灵活的组合。
<template>
<div>
<h4>Home: {{ $store.state.counter }}</h4>
<h4>Home: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
<h4>Age: {{ hanggeAge }}</h4>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
counter() {
return this.$store.state.counter
},
// mapState 函数的解构
...mapState(['name']),
...mapState({
hanggeAge: 'age'
})
}
}
</script>
(2)运行效果如下:

三、在 Composition API 中读取 state
1,使用 useStore 获取 store
(1)在 Vue 3 的 Composition API 中,我们可以使用 useStore 函数来获取 store 对象。这是 Options API 中 this.$store 的等效替代。
(2)下面是在 setup 函数中读取状态的示例。可以看到,store 对象由 this.$store 改为从 useStore 函数获取,计算属性改成 computed 函数。
提示:为了保持响应式,必须使用 computed 函数包裹状态值。直接返回 store.state.counter 会丢失响应性。
<template>
<div>
<h4>Setup: {{ $store.state.counter }}</h4>
<h4>Setup: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
<h4>Age: {{ hanggeAge }}</h4>
</div>
</template>
<script>
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()
// 在 computed 中通过 store 读取状态
const counter = computed(() => store.state.counter)
const name = computed(() => store.state.name)
const hanggeAge = computed(() => store.state.age)
return {
counter,
name,
hanggeAge
}
}
}
</script>

2,在 setup 中使用 mapState
(1)在 setup 语法中,mapState 函数返回的对象需要经过处理后才能使用。因为 mapState 返回的是普通函数,需要绑定 this 并转换为计算属性。
(2)这里使用 mapState 辅助函数对 state 中的 counter、name 和 age 变量进行映射。
- 由于这里使用的是 setup 语法,mapState 函数返回的对象赋值给了 storeStateFns 变量,而不是 computed 选项。因此,我们需要将 storeStateFns 对象里的函数转换成计算属性,以便在 template 中使用。
- 接着,遍历 storeStateFns 对象,取出对象中的每个函数,并为函数绑定 this(即绑定 { $store: store } 对象)。
- 然后,将这些函数转换成计算属性函数,并赋值给 storeState 对象。
- 最后,在 return 语句中将 storeState 对象解构并返回。
<template>
<div>
<h4>Setup: {{ $store.state.counter }}</h4>
<h4>Setup: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
<h4>Age: {{ age }}</h4>
</div>
</template>
<script>
import { mapState, useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()
const storeStateFns = mapState(["counter", "name", "age"])
// storeStateFns 打印为: { counter: function, name: function, ... }
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
// 绑定 this 为 { $store: store }
const fn = storeStateFns[fnKey].bind({ $store: store })
// 将普通函数转换为计算属性函数
storeState[fnKey] = computed(fn)
})
// storeState 打印为: { counter: ref, name: ref, ... }
return { ...storeState }
}
}
</script>
附、mapState 的封装
1,自定义 useState Hook 函数
(1)在 setup 语法中,虽然 Vuex 并没有提供更方便的使用 mapState 函数的方式,但我们可以将 mapState 的使用方式封装成一个通用的 Hook 函数,以方便在 setup 中使用。
(2)首先,创建 hooks/useState.js 文件,代码如下。该函数接收一个 mapper 参数(可以是数组或对象),并将该参数传递给 mapState 函数。
import { computed } from 'vue'
import { mapState, useStore } from 'vuex'
// 自定义一个 useState Hook 函数
export function useState(mapper) {
// 获取 store 对象
const store = useStore()
// 获取映射后的对象
const storeStateFns = mapState(mapper)
// 将普通函数转成计算属性函数
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
const fn = storeStateFns[fnKey].bind({ $store: store })
storeState[fnKey] = computed(fn)
})
return storeState
}
(3)接着创建创建 hooks/index.js 文件,代码如下。该文件主要用于统一导出项目中所有的 Hook 函数,旨在规范导出方式。
import { useState } from './useState'
export { useState }
2,使用封装后的 useState
(1)封装完成后,我们就可以在组件中非常方便地使用 useState 函数来获取 state 中的数据:
- 首先导入 useState Hook 函数,然后使用该函数获取 state 中的 counter 变量。useState 函数会返回一个 storeState 对象,该对象中的函数都是计算属性。因此,在 return 语句中,我们可以直接解构 storeState 对象并返回。
- 接下来,再次使用 useState 这个 Hook 函数获取 state 中的 name 和 age,但这次传递给 useState 函数的是一个对象,而不是数组。
<template>
<div>
<h4>useState: {{ $store.state.counter }}</h4>
<h4>useState: {{ counter }}</h4>
<h4>Name: {{ name }}</h4>
<h4>Age: {{ age }}</h4>
</div>
</template>
<script>
// 导入自定义 Hook
import { useState } from './hooks'
export default {
setup() {
// 使用数组参数
const storeState = useState(["counter"])
// 使用对象参数
const storeState2 = useState({
name: state => state.name,
age: state => state.age
})
return {
...storeState,
...storeState2
}
}
}
</script>
(2)运行效果如下,可用看到通过封装 useState Hook,我们可以在 Composition API 中像使用 Options API 的 mapState 一样方便地获取状态数据。
全部评论(0)