在 Vue 3 的组合式 API 风格中,Vuex 仍然是集中式状态管理工具,但使用方式更加灵活。不过官方已推荐新一代状态库 **Pinia**(可视为 Vuex 5),建议在新项目中使用 Pinia。以下是 Vuex 4(支持 Vue 3)的使用方法:
---
### 一、Vuex 核心概念
- **State**: 全局状态数据
- **Mutations**: 同步修改状态的方法
- **Actions**: 异步操作,触发 Mutations
- **Getters**: 计算属性
---
### 二、基本使用步骤
#### 1. 安装 Vuex
```bash
npm install vuex@next --save
```
#### 2. 创建 Store
```js
// store/index.js
import { createStore } from 'vuex';
export default createStore({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => commit('increment'), 1000);
}
},
getters: {
doubleCount: (state) => state.count * 2
}
});
```
#### 3. 在组件中使用(组合式 API)
```vue
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
// 获取 State(使用计算属性保持响应式)
const count = computed(() => store.state.count);
// 使用 Getter
const doubleCount = computed(() => store.getters.doubleCount);
// 触发 Mutation
const increment = () => store.commit('increment');
// 触发 Action
const asyncIncrement = () => store.dispatch('asyncIncrement');
</script>
<template>
<div>{{ count }}</div>
<div>{{ doubleCount }}</div>
<button @click="increment">+1</button>
<button @click="asyncIncrement">Async +1</button>
</template>
```
---
### 三、组合式封装
可将 Store 逻辑封装到自定义 Hook 中,提高复用性:
```js
// composables/useCounter.js
import { computed } from 'vue';
import { useStore } from 'vuex';
export default function useCounter() {
const store = useStore();
return {
count: computed(() => store.state.count),
doubleCount: computed(() => store.getters.doubleCount),
increment: () => store.commit('increment'),
asyncIncrement: () => store.dispatch('asyncIncrement')
};
}
```
在组件中使用:
```vue
<script setup>
import useCounter from './composables/useCounter';
const { count, doubleCount, increment, asyncIncrement } = useCounter();
</script>
```
---
### 四、替代方案:Pinia(推荐)
Pinia 是 Vue 官方推荐的状态库,专为组合式 API 设计,更简洁:
#### 1. 安装 Pinia
```bash
npm install pinia
```
#### 2. 定义 Store
```js
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++;
},
async asyncIncrement() {
setTimeout(() => this.increment(), 1000);
}
},
getters: {
doubleCount: (state) => state.count * 2
}
});
```
#### 3. 在组件中使用
```vue
<script setup>
import { useCounterStore } from '@/stores/counter';
import { storeToRefs } from 'pinia';
const counterStore = useCounterStore();
const { count, doubleCount } = storeToRefs(counterStore); // 保持响应式
const increment = () => counterStore.increment();
const asyncIncrement = () => counterStore.asyncIncrement();
</script>
```
---
### 五、总结
- **Vuex 4**:适用于已有 Vuex 的项目迁移到 Vue 3,但 API 略显繁琐。
- **Pinia**:新项目首选,完美契合组合式 API,代码更简洁,无 `mutations`,支持 TypeScript。
根据项目需求选择,新项目建议直接使用 Pinia。
在 Vue 3 + TypeScript + 组合式 API 中使用 Vuex Modules,需要特别注意类型系统的支持。以下是完整的配置和使用示例:
---
### 一、项目结构(TypeScript 适配)
```bash
src/
├── store/
│ ├── index.ts # 主 Store
│ ├── modules/
│ │ ├── user.ts # 用户模块
│ │ └── product.ts # 商品模块
```
---
### 二、定义带类型的 Vuex 模块
#### 1. 用户模块定义 `store/modules/user.ts`
```typescript
import { Module } from 'vuex';
// 定义模块的 State 类型
interface UserState {
name: string;
age: number;
}
const userModule: Module<UserState, any> = {
namespaced: true,
// 使用箭头函数返回初始化状态(确保类型安全)
state: (): UserState => ({
name: 'Guest',
age: 0
}),
mutations: {
SET_NAME(state, payload: string) {
state.name = payload;
},
SET_AGE(state, payload: number) {
state.age = payload;
}
},
actions: {
async fetchUser({ commit }, userId: number) {
const user = await mockApi(userId);
commit('SET_NAME', user.name);
commit('SET_AGE', user.age);
}
},
getters: {
greeting: (state) => `Hello, ${state.name}!`
}
};
// 模拟 API 请求
async function mockApi(userId: number): Promise<{ name: string; age: number }> {
return { name: 'Alice', age: 25 };
}
export default userModule;
```
---
### 三、创建主 Store `store/index.ts`
```typescript
import { createStore, Store, ModuleTree } from 'vuex';
import userModule, { UserState } from './modules/user';
// 定义根 State 类型
interface RootState {
// 其他全局状态...
user: UserState; // 模块状态会被合并到这里
}
// 自动推断模块类型
const modules: ModuleTree<RootState> = {
user: userModule
};
const store = createStore<RootState>({
modules
});
// 定义带类型提示的 useStore 函数
export function useStore(): Store<RootState> {
return store;
}
export default store;
```
---
### 四、在组件中使用(组合式 API + TS)
#### 组件示例 `components/UserProfile.vue`
```vue
<script setup lang="ts">
import { computed } from 'vue';
import { useStore } from '@/store';
const store = useStore();
// 访问模块 state(类型自动推断)
const userName = computed(() => store.state.user.name);
// 调用模块 mutation(需要完整命名空间路径)
const updateName = () => store.commit('user/SET_NAME', 'Bob');
// 调用模块 action(类型安全参数)
const loadUser = () => store.dispatch('user/fetchUser', 123);
// 使用模块 getter
const greeting = computed(() => store.getters['user/greeting'] as string);
</script>
<template>
<div>Name: {{ userName }}</div>
<div>{{ greeting }}</div>
<button @click="updateName">Update Name</button>
<button @click="loadUser">Load User</button>
</template>
```
---
### 五、增强类型提示(可选)
#### 1. 创建类型化辅助函数 `store/helpers.ts`
```typescript
import { useStore } from '@/store';
import { computed } from 'vue';
export function useUserModule() {
const store = useStore();
return {
// State
userName: computed(() => store.state.user.name),
userAge: computed(() => store.state.user.age),
// Mutations
setName: (name: string) => store.commit('user/SET_NAME', name),
// Actions
fetchUser: (userId: number) => store.dispatch('user/fetchUser', userId),
// Getters
greeting: computed(() => store.getters['user/greeting'] as string)
};
}
```
#### 在组件中使用:
```vue
<script setup lang="ts">
import { useUserModule } from '@/store/helpers';
const { userName, greeting, fetchUser } = useUserModule();
</script>
```
---
### 六、处理模块间的类型交互
#### 1. 在模块中访问根状态(类型安全)
```typescript
// 在 user 模块的 action 中:
actions: {
someAction({ commit, rootState }) {
// 需要明确类型断言
const globalToken = (rootState as RootState).token;
}
}
```
#### 2. 全局 Action/Mutation 的类型定义
```typescript
// store/index.ts
interface RootState {
token: string; // 添加全局状态
}
// 添加全局 mutations/actions
const store = createStore<RootState>({
state: () => ({
token: 'initial_token'
}),
mutations: {
SET_TOKEN(state, payload: string) {
state.token = payload;
}
},
modules
});
```
---
### 七、注意事项
1. **命名空间必要性**
必须启用 `namespaced: true`,否则模块的 mutations/actions 会全局注册,失去类型安全
2. **严格类型检查**
在 `tsconfig.json` 中启用严格模式:
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": false // Vuex 可能需要这个设置为 false
}
}
```
3. **类型断言的使用**
在访问 `rootState` 或跨模块操作时,可能需要使用类型断言
---
### 八、对比 Pinia(推荐替代方案)
Pinia 的 TypeScript 支持更优秀,天然适配组合式 API:
```typescript
// stores/user.ts
import { defineStore } from 'pinia';
interface UserState {
name: string;
age: number;
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
name: 'Guest',
age: 0
}),
actions: {
async fetchUser(userId: number) {
const user = await mockApi(userId);
this.name = user.name;
this.age = user.age;
}
},
getters: {
greeting: (state) => `Hello, ${state.name}!`
}
});
```
**优势**:
- 自动类型推导
- 无命名空间路径
- 更简洁的 API
- 原生组合式 API 支持
---
### 总结
| 操作 | Vuex 4 + TS | Pinia + TS |
|--------------------|--------------------------------------|------------------------------|
| 模块定义 | 需要手动处理类型和命名空间 | 每个 Store 天然独立 |
| 类型提示 | 需要较多类型断言 | 完全自动推断 |
| 组合式 API 友好度 | 需要包装 `useStore` | 直接通过函数调用 |
| 代码量 | 较多模板代码 | 简洁直观 |
**建议**:新项目应优先选择 Pinia,既有 Vuex 项目可参考上述模式维护。如果必须使用 Vuex,推荐通过封装辅助函数增强类型安全。