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

 

在 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,推荐通过封装辅助函数增强类型安全。

  

 

posted on 2025-01-27 09:31  是水饺不是水饺  阅读(98)  评论(0)    收藏  举报

导航