Pinia 使用實踐
在多品牌平台專案中,Pinia Store 在組件和 Composables 中的使用方式,以及 Store 之間的通訊模式。
1. 面試回答主軸
- 組件使用:使用
storeToRefs保持響應性,Actions 可以直接解構。 - Composables 組合:在 Composables 中組合多個 Store,封裝業務邏輯。
- Store 通訊:推薦在 Composable 中組合,避免循環依賴。
2. 在組件中使用 Store
2.1 基本使用
<script setup lang="ts">
import { useAuthStore } from 'stores/authStore';
// 直接使用 store 實例
const authStore = useAuthStore();
// 訪問 state
console.log(authStore.access_token);
// 調用 action
authStore.setToptVerified(true);
// 訪問 getter
console.log(authStore.isLogin);
</script>
2.2 使用 storeToRefs 解構(重要!)
<script setup lang="ts">
import { useAuthStore } from 'stores/authStore';
import { storeToRefs } from 'pinia';
const authStore = useAuthStore();
// ❌ 錯誤:會失去響應性
const { access_token, isLogin } = authStore;
// ✅ 正確:保持響應性
const { access_token, isLogin } = storeToRefs(authStore);
// ✅ Actions 可以直接解構(不需要 storeToRefs)
const { setToptVerified } = authStore;
</script>
為什麼直接解構會失去響應性?
- Pinia 的 state 和 getters 是響應式的
- 直接解構會破壞響應式連接
storeToRefs會將每個屬性轉換為ref,保持響應性- Actions 本身不是響應式的,所以可以直接解構
3. 在 Composables 中使用 Store
3.1 實際案例:useGame.ts
Composables 是組合 Store 邏輯的最佳場所。
import { useGameStore } from 'stores/gameStore';
import { useProductStore } from 'stores/productStore';
import { storeToRefs } from 'pinia';
export function useGame() {
// 1️⃣ 引入多個 stores
const gameStore = useGameStore();
const productStore = useProductStore();
// 2️⃣ 解構 state 和 getters(使用 storeToRefs)
const { gameState } = storeToRefs(gameStore);
const { productState } = storeToRefs(productStore);
// 3️⃣ 解構 actions(直接解構)
const { initAllGameList, updateAllGameList } = gameStore;
// 4️⃣ 組合邏輯
async function initGameTypeList() {
const { status, data } = await useApi(getGameTypes);
if (status) {
setGameTypeList(data.list);
setGameTypeMap(data.map);
}
}
// 5️⃣ 返回給組件使用
return {
gameState,
productState,
initGameTypeList,
initAllGameList,
};
}
面試重點:
- Composables 是組合 Store 邏輯的最佳場所
- 使用
storeToRefs確保響應性 - Actions 可以直接解構
- 將複雜的業務邏輯封裝在 composable 中
4. Store 之間的通訊
4.1 方法一:在 Store 內部調用其他 Store
import { defineStore } from 'pinia';
import { useUserInfoStore } from './userInfoStore';
export const useAuthStore = defineStore('authStore', {
actions: {
async login(credentials) {
const { status, data } = await api.login(credentials);
if (status) {
this.access_token = data.access_token;
// 調用其他 store 的方法
const userInfoStore = useUserInfoStore();
userInfoStore.setStoreUserInfo(data.user);
}
},
},
});
4.2 方法二:在 Composable 中組合多個 Store(推薦)
export function useInit() {
const authStore = useAuthStore();
const userInfoStore = useUserInfoStore();
const gameStore = useGameStore();
async function initialize() {
// 依序執行多個 store 的初始化
await authStore.checkAuth();
if (authStore.isLogin) {
await userInfoStore.getUserInfo();
await gameStore.initGameList();
}
}
return { initialize };
}
面試重點:
- ✅ 推薦在 Composable 中組合多個 Store
- ❌ 避免 Store 之間的循環依賴
- 🎯 保持 Store 的單一職責原則
5. 實戰案例:用戶登入流程
這是一個完整的 Store 使用流程,涵蓋了多個 Store 的協作。
5.1 流程圖
用戶點擊登入按鈕
↓
調用 useAuth().handleLogin()
↓
API 請求登入
↓
成功 → authStore 儲存 token
↓
useUserInfo().getUserInfo()
↓
userInfoStore 儲存用戶資訊
↓
useGame().initGameList()
↓
gameStore 儲存遊戲列表
↓
跳轉到首頁
5.2 程式碼實作
// 1️⃣ authStore.ts - 管理認證狀態
export const useAuthStore = defineStore('authStore', {
state: () => ({
access_token: undefined as string | undefined,
user_id: undefined as number | undefined,
}),
getters: {
isLogin: (state) => !!state.access_token,
},
persist: true, // 持久化認證資訊
});
// 2️⃣ userInfoStore.ts - 管理用戶資訊
export const useUserInfoStore = defineStore('useInfoStore', {
state: () => ({
info: {} as Response.UserInfo,
}),
actions: {
setStoreUserInfo(userInfo: Response.UserInfo) {
this.info = userInfo;
},
},
persist: false, // 不持久化(敏感資訊)
});
// 3️⃣ useAuth.ts - 組合認證邏輯
export function useAuth() {
const authStore = useAuthStore();
const { access_token } = storeToRefs(authStore);
const { isLogin } = storeToRefs(authStore);
async function handleLogin(credentials: LoginCredentials) {
const { status, data } = await api.login(credentials);
if (status) {
// 更新 authStore
authStore.$patch({
access_token: data.access_token,
user_id: data.user_id,
});
return true;
}
return false;
}
return {
access_token,
isLogin,
handleLogin,
};
}
// 4️⃣ LoginPage.vue - 登入頁面
<script setup lang="ts">
import { useAuth } from 'src/common/hooks/useAuth';
import { useUserInfo } from 'src/common/composables/useUserInfo';
import { useGame } from 'src/common/composables/useGame';
import { useRouter } from 'vue-router';
const { handleLogin } = useAuth();
const { getUserInfo } = useUserInfo();
const { initGameList } = useGame();
const router = useRouter();
const onSubmit = async (formData: LoginForm) => {
// 步驟 1: 登入
const success = await handleLogin(formData);
if (success) {
// 步驟 2: 獲取用戶資訊
await getUserInfo();
// 步驟 3: 初始化遊戲列表
await initGameList();
// 步驟 4: 跳轉首頁
router.push('/');
}
};
</script>
面試重點:
-
職責分離
authStore: 只管理認證狀態userInfoStore: 只管理用戶資訊useAuth: 封裝認證相關業務邏輯useUserInfo: 封裝用戶資訊相關業務邏輯
-
響應式數據流
- 使用
storeToRefs保持響應性 - Store 更新會自動觸發組件更新
- 使用
-
持久化策略
authStore持久化(用戶刷新頁面後保持登入)userInfoStore不持久化(安全考量)
6. 面試重點整理
6.1 storeToRefs 的使用
可以這樣回答:
在組件中使用 Pinia Store 時,如果要解構 state 和 getters,必須使用
storeToRefs保持響應性。直接解構會破壞響應式連接,因為 Pinia 的 state 和 getters 是響應式的。storeToRefs會將每個屬性轉換為ref,保持響應性。Actions 可以直接解構,不需要storeToRefs,因為它們本身不是響應式的。
關鍵點:
- ✅
storeToRefs的作用 - ✅ 為什麼需要
storeToRefs - ✅ Actions 可以直接解構
6.2 Store 之間通訊
可以這樣回答:
Store 之間的通訊有兩種方式:1) 在 Store 內部調用其他 Store,但要注意避免循環依賴;2) 在 Composable 中組合多個 Store,這是推薦的方式。最佳實踐是保持 Store 的單一職責原則,將複雜的業務邏輯封裝在 Composable 中,避免 Store 之間的直接依賴。
關鍵點:
- ✅ 兩種通訊方式
- ✅ 推薦在 Composable 中組合
- ✅ 避免循環依賴
7. 面試總結
可以這樣回答:
在專案中使用 Pinia Store 時,有幾個關鍵實踐:1) 在組件中使用
storeToRefs解構 state 和 getters,保持響應性;2) 在 Composables 中組合多個 Store,封裝業務邏輯;3) Store 之間的通訊推薦在 Composable 中組合,避免循環依賴;4) 保持 Store 的單一職責原則,將複雜邏輯放在 Composable 中。
關鍵點:
- ✅
storeToRefs的使用 - ✅ Composables 組合 Store
- ✅ Store 通訊模式
- ✅ 職責分離原則