在上一回的修煉中,我們初探了 Pinia 的核心,學會了使用 state
、getters
和 actions
來建立一個管理認證 token
的中央倉庫 (authStore
)。這解決了跨元件和路由守衛的狀態共享問題。
但目前為止,我們的應用程式只知道「有沒有人登入」,卻不知道「登入的人是誰」。Header
元件無法顯示使用者名稱,儀表板也無法根據不同的使用者角色顯示對應的功能。顯然,我們的 authStore
還不夠完整。
今天,我們會將 authStore
升級,讓它不只是一個 token
的容器,而是一個能完整管理使用者狀態與資料的中心。
我們的目標是讓 authStore
也能儲存從後端獲取的使用者個人資料(例如:姓名、Email、角色等)。
讓我們來擴充 src/stores/auth.js
:
// src/stores/auth.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
token: localStorage.getItem('pos-auth-token') || null,
// 新增一個 state 來儲存使用者資訊
user: null,
}),
getters: {
isLoggedIn: (state) => !!state.token,
// 新增 getters 來方便地存取使用者資料
userName: (state) => state.user?.name || '訪客',
userAvatar: (state) => state.user?.avatar || 'default-avatar.png',
userRoles: (state) => state.user?.roles || [],
},
actions: {
// ...
},
})
我們在 state
中新增了 user: null
作為初始值,並在 getters
中新增了 userName
、userAvatar
等。使用 ?.
(Optional Chaining) 和 ||
可以確保即使 state.user
是 null
,我們的程式碼也不會報錯,而是會有一個降級(fallback)結果(例如顯示「訪客」)。
儲存使用者資料最好的時機,就是在登入成功的那一刻。通常,一個設計良好的後端登入 API,在驗證成功後,會同時回傳 token
和該名使用者的基本資料。
因此,我們需要修改 login
action,讓它能接收並儲存這兩份資料。
// src/stores/auth.js actions
// ...
actions: {
// 讓 login action 同時接收 token 和 user 物件
login(token, user) {
this.token = token;
this.user = user;
localStorage.setItem('pos-auth-token', token);
},
logout() {
this.token = null;
this.user = null;
localStorage.removeItem('pos-auth-token');
},
},
// ...
相對地,在 LoginView.vue
中,當我們呼叫後端 API 並驗證成功後,就應該這樣呼叫 login
action:
// LoginView.vue
import { useAuthStore } from '@/stores/auth';
const authStore = useAuthStore();
async function handleBackendVerification() {
// ...
const { appToken, userProfile } = await backendResponse.json();
// 將 token 和 userProfile 一起傳入 action
authStore.login(appToken, userProfile);
router.push('/dashboard');
}
現在我們遇到了一個 SPA 中非常經典的問題:當使用者在 /dashboard
頁面按下 F5 重新整理時,會發生什麼事?
authStore
被重新建立。state.token
因為我們有 localStorage
,所以成功被恢復。state.user
變回了初始值 null
!這會導致畫面上本來顯示「歡迎,Noopy」的地方,瞬間變回「歡迎,訪客」。這不是我們想要的!
解決方案:在應用程式初始化時,如果發現有 token
存在,就應該主動向後端發起一個請求,來獲取當前登入者的資料。
讓我們在 authStore
中新增一個 fetchUserProfile
action:
// src/stores/auth.js actions
// ...
actions: {
// ... login, logout
// 這個 action 用於在應用初始化時恢復使用者狀態
async fetchUserProfile() {
// 只有在 token 存在時才執行
if (this.token) {
try {
// 假設我們有一個 API client 和一個 /api/me 的端點
// const response = await apiClient.get('/me');
// this.user = response.data;
// --- 模擬 API 回應 ---
const mockUser = { name: 'Noopy', email: 'test@example.com', roles: ['admin'] };
this.user = mockUser;
// --------------------
} catch (error) {
console.error('獲取使用者資料失敗', error);
// 如果 token 無效或過期,API 可能會回傳 401
// 此時應該將使用者登出
this.logout();
}
}
},
},
// ...
那麼,該在哪裡呼叫這個 fetchUserProfile
action 呢?
最佳地點就是我們應用的最頂層元件——App.vue
。因為它只會在應用程式第一次載入時被掛載一次。
// src/App.vue
<script setup>
import { onMounted } from 'vue';
import { useAuthStore } from './stores/auth';
const authStore = useAuthStore();
onMounted(() => {
// 在元件掛載後,嘗試獲取使用者資料
authStore.fetchUserProfile();
});
</script>
<template>
<router-view />
</template>
透過這個機制,每次使用者打開或重新整理我們的應用,App.vue
都會確保去檢查並恢復使用者的登入資料,讓我們的 UI 狀態保持一致!
今天,我們的 authStore
真正進化成了名副其實的「使用者狀態」管理者。我們學到了:
state
中同時儲存 token
和 user
物件。login
action 中一次性地更新所有認證相關狀態。App.vue
的 onMounted
Hook中呼叫一個 action (fetchUserProfile
),來處理頁面重新整理時的狀態恢復問題。現在,我們的應用程式不僅知道「有沒有人」,還知道「那個人是誰」,並且能在任何時候都記住他。
有了可靠的狀態,下一步,我們就該建立一個可靠的管道來跟後端要資料了。沒錯,
明日,Day 22:[APIの呼吸・壹之型] Backend連接 - Axios設定與封裝