在上一回的修煉中,我們為了讓路由守衛能得知「使用者是否登入」,引入了一個名為 useAuthStore
的東西。我們只是簡單地使用了它,但你心中可能充滿了疑問:這是什麼東西?它從何而來?又為何要用它?
今天,我們將深入了解 Vue 官方推薦的狀態管理函式庫——Pinia,理解它如何幫助我們管理散落在應用各處的「全域狀態」。
想像一下,我們的銷售系統越來越複雜,許多元件都需要共用同一份資料:
Header
元件需要顯示使用者名稱。SideBar
元件需要根據使用者權限來顯示不同的選單。Profile
頁面需要讀取並修改使用者的個人資料。router
需要知道使用者是否登入來決定是否放行。如果沒有一個統一的管理機制,我們可能會陷入「Props 地獄 (Prop Drilling)」——將資料作為 props 從父元件一層一層地往下傳遞,即使中間的好幾層元件根本用不到這些資料。這會讓程式碼變得極難維護。
為了解決這個問題,「全域狀態管理」應運而生。它的核心思想是:
將共用的狀態抽離出來,放置在一個獨立於所有元件的「中央倉庫 (Store)」中。任何元件都可以直接從這個倉庫讀取或修改狀態,而無需透過 props 傳遞。
而 Pinia,就是 Vue 3 生態中最閃亮、最受推薦的那個「中央倉庫」。
Pinia 的設計非常簡潔直觀,你可以把每一個 Store 都想像成一個獨立的模組,它由三個核心部分組成:state
、getters
和 actions
。
讓我們再次請出 Day 19 的 auth.js
,以它為範例來解構 Pinia Store。
// src/stores/auth.js
import { defineStore } from 'pinia'
// 使用 defineStore() 來定義一個 Store
// 第一個參數是這個 Store 的唯一 ID
export const useAuthStore = defineStore('auth', {
// ... 核心三本柱
})
state
是 Store 的心臟,是存放資料的地方。它必須是一個函式,並且回傳一個物件,這個物件就包含了這個 Store 的初始狀態。
// ...
state: () => ({
// 我們假設 token 存在 localStorage 中
token: localStorage.getItem('pos-auth-token') || null,
// 我們也可以在這裡存放使用者資訊
userInfo: null,
}),
// ...
state
之所以是函式,是為了避免在伺服器端渲染 (SSR) 時造成交叉請求的狀態污染。getters
就像是 Store 的「計算屬性 (Computed Properties)」。它們可以用來基於 state
的值,衍伸出新的資料。getters
的值會被快取,只有當它依賴的 state
發生變化時,才會重新計算。
// ...
getters: {
// getter 接收 state 作為第一個參數
isLoggedIn: (state) => !!state.token,
// 也可以使用 this 來存取 state
userName: (state) => state.userInfo?.name || 'Guest',
},
// ...
isLoggedIn
就是一個完美的例子。我們不需要在每個元件中都寫一次 !!authStore.token
,而是可以直接取用這個 getter,讓程式碼更簡潔。actions
就像是 Store 的「方法 (Methods)」。它們是唯一推薦用來修改 state
的地方。Action 可以是同步的,也可以是異步的。
// ...
actions: {
// 登入成功後呼叫此 action
login(token, user) {
// 在 action 中,我們可以用 `this` 來存取 state
this.token = token;
this.userInfo = user;
localStorage.setItem('pos-auth-token', token);
},
// 登出時呼叫此 action
logout() {
this.token = null;
this.userInfo = null;
localStorage.removeItem('pos-auth-token');
},
// 也可以是 async action
async fetchUserProfile() {
// const user = await api.getUser();
// this.userInfo = user;
}
},
// ...
state
的邏輯都封裝在 actions
中,可以讓我們的狀態變更流程更可預測、更易於追蹤和除錯。定義好 Store 之後,在元件中使用它就非常簡單了。
<script setup>
import { useAuthStore } from '@/stores/auth';
import { storeToRefs } from 'pinia';
// 取得 store 實例
const authStore = useAuthStore();
//使用 storeToRefs 來保持響應性
const { isLoggedIn, userName } = storeToRefs(authStore);
function handleLogout() {
authStore.logout();
}
</script>
<template>
<div v-if="isLoggedIn">
<p>歡迎, {{ userName }}</p>
<button @click="handleLogout">登出</button>
</div>
</template>
storeToRefs
:這是一個非常重要的輔助函式。如果你想從 Store 中解構 state
或 getters
並在模板中使用,同時又希望它們保持響應性,就必須使用 storeToRefs
。它會將每一個屬性都轉換成一個 .value
的 ref
。authStore.logout()
即可。今天,我們正式認識了 Pinia 這位強大的狀態管理夥伴。我們學到了:
state
(資料來源)、getters
(衍伸資料) 和 actions
(修改資料的方法)。useAuthStore()
取得實例,並使用 storeToRefs
來安全地解構響應式資料。Pinia 以其簡潔的 API、完整的 TypeScript 支援和強大的 DevTools 整合,成為了 Vue 3 開發的首選。掌握了它,就等於掌握了管理複雜應用狀態的key。
明日,Day 21:[Stateの呼吸・貳之型] User State - 管理登入狀態與資料。心を燃やせ 🔥!