Vuex vs Pinia 差異比較
比較 Vuex 和 Pinia 的核心差異,包含 API 設計、TypeScript 支援、模組化方式等,並提供遷移指南。
1. 面試回答主軸
- 核心差異:Vuex 需要 mutations,Pinia 不需要;Pinia 有更好的 TypeScript 支援;模組化方式不同。
- 選擇建議:Vue 3 新專案推薦 Pinia,Vue 2 專案使用 Vuex。
- 遷移考量:從 Vuex 遷移到 Pinia 的步驟與注意事項。
2. 核心差異總覽
| 特性 | Vuex | Pinia |
|---|---|---|
| Vue 版本 | Vue 2 | Vue 3 |
| API 複雜度 | 較複雜(需要 mutations) | 更簡潔(不需要 mutations) |
| TypeScript 支援 | 需要額外配置 | 原生完整支援 |
| 模組化 | 嵌套模組 | 扁平化,每個 store 獨立 |
| 體積 | 較大 | 更小(約 1KB) |
| 開發體驗 | 良好 | 更好(HMR、Devtools) |
3. API 差異比較
3.1 Mutations vs Actions
Vuex:需要 mutations 來同步修改 state
// Vuex
export default createStore({
state: { count: 0 },
mutations: {
INCREMENT(state) {
state.count++;
},
},
actions: {
increment({ commit }) {
commit('INCREMENT');
},
},
});
Pinia:不需要 mutations,直接在 actions 中修改 state
// Pinia
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++; // 直接修改
},
},
});
關鍵差異:
- Vuex:必須透過
mutations同步修改 state,actions透過commit調用mutations - Pinia:不需要
mutations,actions可以直接修改 state(同步或非同步都可以)
3.2 State 定義
Vuex:state 可以是物件或函數
state: {
count: 0,
}
Pinia:state 必須是函數,避免多實例共享狀態
state: () => ({
count: 0,
})
3.3 Getters
Vuex:getters 接收 (state, getters) 作為參數
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne: (state, getters) => getters.doubleCount + 1,
}
Pinia:getters 可以使用 this 訪問其他 getters
getters: {
doubleCount: (state) => state.count * 2,
doubleCountPlusOne(): number {
return this.doubleCount + 1;
},
}
3.4 在組件中使用
Vuex:使用 mapState、mapGetters、mapActions 輔助函數
computed: {
...mapState(['count']),
...mapGetters(['doubleCount']),
},
methods: {
...mapActions(['increment']),
}
Pinia:直接使用 store 實例,使用 storeToRefs 保持響應性
const store = useCounterStore();
const { count, doubleCount } = storeToRefs(store);
const { increment } = store;
4. 模組化差異
4.1 Vuex Modules(嵌套模組)
Vuex:使用嵌套模組,需要 namespaced: true
// stores/user.js
export default {
namespaced: true,
state: { name: 'John' },
mutations: {
SET_NAME(state, name) {
state.name = name;
},
},
};
// 在組件中使用
this.$store.dispatch('user/SET_NAME', 'Jane'); // 需要命名空間前綴
4.2 Pinia Stores(扁平化)
Pinia:每個 store 都是獨立的,無需嵌套
// stores/user.ts
export const useUserStore = defineStore('user', {
state: () => ({ name: 'John' }),
actions: {
setName(name: string) {
this.name = name;
},
},
});
// 在組件中使用
const userStore = useUserStore();
userStore.setName('Jane'); // 直接調用,無需命名空間
關鍵差異:
- Vuex:需要嵌套模組,使用
namespaced: true,調用時需要命名空間前綴 - Pinia:每個 store 獨立,無需命名空間,直接調用
5. TypeScript 支援差異
5.1 Vuex TypeScript 支援
Vuex:需要額外配置型別
// stores/types.ts
export interface State {
count: number;
user: { name: string; age: number };
}
// stores/index.ts
import { createStore, Store } from 'vuex';
import { State } from './types';
export default createStore<State>({
state: { count: 0, user: { name: 'John', age: 30 } },
});
// 在組件中使用
const store = useStore<State>();
// 需要手動定義型別,沒有完整的型別推斷
5.2 Pinia TypeScript 支援
Pinia:原生完整支援,自動型別推斷
// stores/counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
user: { name: 'John', age: 30 },
}),
getters: {
doubleCount: (state) => state.count * 2, // 自動推斷型別
},
actions: {
increment() {
this.count++; // 完整的型別推斷和自動完成
},
},
});
// 在組件中使用
const store = useCounterStore();
store.count; // 完整的型別推斷
store.doubleCount; // 完整的型別推斷
store.increment(); // 完整的型別推斷
關鍵差異:
- Vuex:需要手動定義型別,型別推斷不完整
- Pinia:原生完整支援,自動型別推斷,開發體驗更好
6. 遷移指南
6.1 基本遷移步驟
- 安裝 Pinia
npm install pinia
- 替換 Vuex Store
// 舊的 Vuex
import { createStore } from 'vuex';
export default createStore({ ... });
// 新的 Pinia
import { createPinia } from 'pinia';
const pinia = createPinia();
app.use(pinia);
- 轉換 Store 定義
// Vuex
export default createStore({
state: { count: 0 },
mutations: {
INCREMENT(state) {
state.count++;
},
},
actions: {
increment({ commit }) {
commit('INCREMENT');
},
},
});
// Pinia
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++;
},
},
});
- 更新組件使用方式
// Vuex
import { mapState, mapActions } from 'vuex';
computed: { ...mapState(['count']) },
methods: { ...mapActions(['increment']) },
// Pinia
import { storeToRefs } from 'pinia';
const store = useCounterStore();
const { count } = storeToRefs(store);
const { increment } = store;
6.2 常見遷移問題
問題 1:如何處理 Vuex modules?
// Vuex modules
modules: {
user: userModule,
product: productModule,
}
// Pinia:每個模組變成獨立的 store
// stores/user.ts
export const useUserStore = defineStore('user', { ... });
// stores/product.ts
export const useProductStore = defineStore('product', { ... });
問題 2:如何處理命名空間?
// Vuex:需要命名空間前綴
this.$store.dispatch('user/SET_NAME', 'John');
// Pinia:直接調用,無需命名空間
const userStore = useUserStore();
userStore.setName('John');
7. 為什麼 Pinia 不需要 mutations?
原因:
-
Vue 3 的響應式系統
- Vue 3 使用 Proxy,可以直接追蹤物件的修改
- 不需要像 Vue 2 那樣透過 mutations 來追蹤狀態變化
-
簡化 API
- 移除 mutations 可以簡化 API,減少樣板程式碼
- Actions 可以直接修改 state,無論是同步還是非同步操作
-
開發體驗
- 減少一層抽象,開發者更容易理解和使用
- 不需要記住
commit和dispatch的區別
範例:
// Vuex:需要 mutations
mutations: { SET_COUNT(state, count) { state.count = count; } },
actions: { setCount({ commit }, count) { commit('SET_COUNT', count); } },
// Pinia:直接修改
actions: { setCount(count) { this.count = count; } },
8. 如何選擇使用 Vuex 還是 Pinia?
選擇建議:
-
新專案
- Vue 3 專案:推薦使用 Pinia
- Vue 2 專案:使用 Vuex
-
現有專案
- Vue 2 + Vuex:可以繼續使用 Vuex,或考慮升級到 Vue 3 + Pinia
- Vue 3 + Vuex:可以考慮遷移到 Pinia(但非必須)
-
專案需求
- 需要完整 TypeScript 支援:選擇 Pinia
- 需要更簡潔的 API:選擇 Pinia