Vue 3 的组合式 API 风格中,Vuex

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
在 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。

  

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
在 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,推荐通过封装辅助函数增强类型安全。

  

 

posted on   是水饺不是水饺  阅读(17)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示