如果有任何問題或建議,歡迎隨時聯繫我:
昨天,我們用 Vue Router 成功地將我們的應用程式劃分成了多個頁面,就像蓋好了一棟有許多房間的房子。但很快地,我們就會遇到新的問題:
這些「跨組件的共享狀態」如果只靠 Props
和 Emit
來層層傳遞,當應用程式一大,就會變成一場災難,我們稱之為「Prop Drilling」。
為了解決這個問題,我們需要一個「中央儲藏室」來統一管理這些共享的狀態。今天,我們就來認識 Vue 3 生態系中最受歡迎的狀態管理庫:Pinia!
想像一下,如果你的 App.vue
有一個 isLoggedIn
的狀態,你需要把它傳給 TheHeader
組件,TheHeader
再傳給 UserMenu
組件,UserMenu
再傳給 Avatar
組件... 這條鏈路只要中間斷了一層,資料就傳不過去了。
如果 Avatar
組件想改變登入狀態,又得反向地用 Emit
一層一層往上傳遞事件,維護起來非常痛苦。
狀態管理庫 (State Management Library) 的作用,就是提供一個全域的、集中的地方來存放你的應用程式狀態。任何組件都可以直接從這個「中央儲藏室」讀取或修改資料,而不需要關心組件之間的層級關係。
Pinia 的設計非常簡潔,被譽為「Vuex 的實質性繼任者」。它的 API 設計非常貼近 Vue 3 的 Composition API,學習起來非常直觀。你只需要掌握它的三大核心元素:State
、Getters
和 Actions
。
一個 "Store" 就是一個獨立的狀態儲藏室,例如 userStore
、cartStore
等。我們使用 defineStore
函式來定義它。
stores/counter.js
import { defineStore } from 'pinia';
// `defineStore` 的第一個參數是這個 store 的唯一 ID
export const useCounterStore = defineStore('counter', {
// State: 存放狀態的地方,必須是一個回傳物件的函式
state: () => ({
count: 0,
name: 'Eduardo',
}),
// Getters: 如同組件中的 computed,用來包裝 state
getters: {
doubleCount: (state) => state.count * 2,
},
// Actions: 如同組件中的 methods,用來修改 state
actions: {
increment() {
// 在 action 中,你可以透過 `this` 來存取 state
this.count++;
},
randomizeCounter() {
this.count = Math.round(100 * Math.random());
},
},
});
在任何組件的 <script setup>
中,你只需要像呼叫一個 Composable 函式一樣,就能取得 Store 的實例。
CounterComponent.vue
<script setup>
import { useCounterStore } from '../stores/counter';
// 取得 store 的實例
const counterStore = useCounterStore();
</script>
<template>
<div>
<!-- 直接存取 state 和 getters -->
<p>Current Count: {{ counterStore.count }}</p>
<p>Double Count: {{ counterStore.doubleCount }}</p>
<!-- 呼叫 actions -->
<button @click="counterStore.increment">Increment</button>
</div>
</template>
看到了嗎?沒有任何 Props 或 Emit,
CounterComponent
直接就和counter
store 溝通了。這就是 Pinia 的魅力:直觀、型別安全、且與 Vue Devtools 完美整合。
除了基礎的 state
、getters
、actions
,當你更深入使用 Pinia 時,會接觸到一些更強大的功能,這裡提供幾個關鍵字讓你未來可以深入研究:
storeToRefs
state
和 getters
解構出來,並保持其響應性 (reactivity)。如果你直接用 const { count } = counterStore
,會失去響應性,但 const { count } = storeToRefs(counterStore)
則不會。這在組合式 API (setup
) 中非常有用。Plugins
localStorage
,實現本地持久化儲存,這樣使用者重新整理頁面後狀態也不會遺失。$patch
state
屬性時,使用 $patch
會更有效率。它可以接受一個物件或一個函式作為參數,進行批次更新,這有助於優化效能,並讓 Vue Devtools 中的紀錄更清晰。$subscribe
state
的變化。每當 state
透過 action
或 $patch
改變時,你設定的回呼函式就會被觸發。這對於實現某些副作用、日誌紀錄或與其他瀏覽器 API 互動非常方便。$onAction
$subscribe
,但它監聽的是 action
的調用。你可以在 action
執行前、執行後或發生錯誤時掛上監聽器,適合用來做全域的錯誤處理、請求的 loading 狀態管理等。思考一:設計購物車 Store
試著設計一個 cartStore
。它應該包含:
state
:一個 items
陣列,用來存放商品物件。getters
:一個 totalPrice
,用來計算購物車中所有商品的總價。actions
:一個 addItem(item)
方法,用來將商品加入 items
陣列。思考二:本地狀態 vs. 全域狀態
什麼時候你應該使用 Pinia 來管理狀態?什麼時候又該使用組件自己的本地狀態 (ref
, reactive
)?有沒有一個判斷的標準?
今天我們學習了如何使用 Pinia 來解決跨組件狀態共享的難題。Pinia 以其簡潔的 API 和強大的功能,成為了 Vue 3 開發的首選狀態管理方案。
defineStore
創建。state
:Store 的核心資料,必須是函式回傳的物件。getters
:Store 的計算屬性,用來衍生出新的狀態。actions
:Store 的方法,用來執行同步或非同步操作來修改狀態。我們現在可以管理來自客戶端的狀態了,但真實世界的應用程式,資料大多來自遠端的伺服器。明天,我們將學習如何串接 API,並將非同步獲取的資料,優雅地整合進我們的 Pinia store 中。敬請期待!