你是否在 Vue 3 專案裡,被跨元件的資料傳遞搞得一個頭兩個大?父元件傳 props 給子元件、子元件用 emit 回傳,一層又一層地「打電話轉接」,只為了讓深層的某個按鈕能讀到頂層的登入狀態或購物車清單。這種「prop 鑽孔(prop drilling)」的痛苦,每個中大型前端專案都逃不掉。
這時候你需要的,不是更複雜的元件設計,而是一個專門管理「全域狀態」的中央倉庫——Pinia。它是 Vue 官方目前推薦的狀態管理方案,取代了過去的 Vuex,對 TypeScript 極度友善,學習曲線也更平緩。
一、Pinia 是什麼?和 Vuex 差在哪?
Pinia 的本質是一個「狀態容器」:把需要跨元件共享的資料(state)、根據資料計算出來的值(getters)、以及修改資料的行為(actions)集中管理。
相較於 Vuex,Pinia 做了幾個讓人鬆一口氣的簡化:
- 去掉 mutations:Vuex 裡改狀態要寫 mutation、呼叫要寫 action,雙層結構很囉唆;Pinia 直接讓 action 改 state。
- 對 TypeScript 原生友好:不需要到處寫
as型別推斷,開發體驗順滑。 - 模組化更直覺:每個 store 就是一個獨立檔案,用
defineStore定義,想拆多細就拆多細。
二、建立第一個 Store
我們用一個最經典的「計數器」來走一遍完整流程。
步驟 1:安裝 Pinia
npm install pinia
步驟 2:在入口檔註冊
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
步驟 3:定義你的第一個 store
// stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
三、在元件中使用 Store
在 <script setup> 裡呼叫 store 函式即可,使用方式和普通的響應式物件幾乎一樣。
步驟 1:引入並建立 store 實例
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
步驟 2:讀取狀態與 getter
<template>
<p>計數:{{ counter.count }}</p>
<p>兩倍:{{ counter.double }}</p>
</template>
步驟 3:呼叫 action 修改狀態
<button @click="counter.increment">+1</button>
重點:如果你要把 state 解構出來用(例如 const { count } = counter),記得用 storeToRefs 包裹,否則會失去響應式:
import { storeToRefs } from 'pinia'
const { count, double } = storeToRefs(counter)
四、實戰進階:拆多個 Store 管理複雜業務
真實專案很少只有一個 store。建議按「業務領域」拆分,例如 useUserStore(登入使用者)、useCartStore(購物車)、useOrderStore(訂單)。不同 store 之間也能互相呼叫:
// stores/cart.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
state: () => ({ items: [] }),
actions: {
checkout() {
const user = useUserStore()
if (!user.isLogin) {
alert('請先登入再結帳')
return
}
// ...呼叫 API 下單
},
},
})
五、常見坑與最佳實踐
- 重點:永遠用
storeToRefs解構 state 與 getters,直接解構會斷開響應式連結。 - 重點:修改狀態盡量走 action,不要在元件裡直接
counter.count++,便於追蹤與除錯。 - 大型專案把 store 按領域拆分,避免單一 store 肥大難維護。
- Pinia 內建 devtools 支援,開發時直接在瀏覽器面板檢查每個 store 的狀態變化,非常好用。
互動話題:你在專案中是怎麼管理跨元件狀態的?是直接用 Pinia,還是還停留在 provide/inject 或 Event Bus?歡迎在留言區分享你的做法與踩過的坑!