Pinia 實戰教學:掌握語法風格與響應式解構技巧
在 Vue 3 的開發世界中,Pinia 提供了極大的靈活性。無論您喜歡傳統的開發模式,還是追求 Composition API 的現代感,Pinia 都能滿足您。本文將帶您深入了解 Pinia 的兩種語法風格,以及在使用時必須注意的 響應式 細節。若還沒安裝與註冊,請先看 Pinia 介紹。
Pinia 的兩種語法風格
Pinia 允許開發者根據習慣選擇 Option Store 或 Setup Store。
Option Store (選項式)
與 Vue 2 的架構相似,適合喜歡將資料 state、計算屬性 getters 與方法 actions 清晰拆分的開發者。
// stores/counter.js
import { defineStore } from 'pinia';
export default defineStore('counter', {
// 儲存資料處,類似 data
state: () => {
return {
count: 0,
};
},
// 計算衍生狀態,類似 computed
getters: {
doubleCount: (state) => state.count * 2,
},
// 修改資料的方法,類似 methods
actions: {
increment() {
this.count++;
},
},
});Setup Store (組合式)
這是偏向 Composition API 的寫作方式,傳入一個函式並回傳需要暴露的屬性,結構更彈性。
// stores/counter.js
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
export default defineStore('counter', () => {
const count = ref(0); // 相當於 state
const doubleCount = computed(() => count.value * 2); // 相當於 getters
function increment() {
// 相當於 actions
count.value++;
}
return {
count,
doubleCount,
increment,
};
});快速對照表
ref()/reactive()→state。computed()→getters。function()→actions。
兩種寫法怎麼選?
功能完全相同,差別在風格與彈性:
- Option Store:區塊分明,
state、getters、actions各就各位,適合團隊有既有習慣時。 - Setup Store:可以直接在裡面使用
watch或其他 Composable,處理複雜邏輯更自由。
要注意 Option Store 的 action 用 this 存取 state,Setup Store 則要記得 .value。同一專案建議 只選一種。
如何在元件中正確使用 Store?
定義完 Store 後,必須在元件中引入並呼叫,才能發揮作用。
// components/Counter.vue
<script setup>
import useCounterStore from "@/stores/counter";
const counterStore = useCounterStore();
</script>
<template>
<div>
<p>計數器: {{ counterStore.count }}</p>
<p>雙倍計數: {{ counterStore.doubleCount }}</p>
<input @click="counterStore.increment" type="button" value="increment">
</div>
</template>不要在模組最外層呼叫 useCounterStore()
defineStore 只是 定義,真正的實體要等 Pinia 安裝完成後才建立。若在 .js 檔的最外層就呼叫 useCounterStore(),會因為此時 Pinia 還沒掛載而報錯。請在元件的 <script setup> 或函式內部呼叫它。
進階:避免解構導致的響應式失效
這是許多 Vue 開發者常遇到的陷阱:store 是一個用 reactive() 包裝的物件。如果您直接對它進行解構(Destructuring),解構出來的資料會失去響應式特性,畫面將不會隨資料更新。
錯誤示範(會失去響應式)
const { count, doubleCount } = counterStore; // 這樣拿到的只是普通的數值,不會聯動更新正確做法:使用 storeToRefs()
為取出 store 中的屬性並保持其響應式,我們必須使用 Pinia 提供的 storeToRefs() 工具。若為 action 或非響應式的屬性,直接解構無妨。如下:
// components/Counter.vue
<script setup>
import { storeToRefs } from "pinia";
import useCounterStore from "@/stores/counter";
const counterStore = useCounterStore();
const { count, doubleCount } = storeToRefs(counterStore);
const { increment } = counterStore;
</script>
<template>
<div>
<p>計數器: {{ count }}</p>
<p>雙倍計數: {{ doubleCount }}</p>
<input @click="increment" type="button" value="increment">
</div>
</template>記法很簡單:資料用 storeToRefs,方法直接解構。原因是函式本身沒有響應式問題,而 state 與 getters 必須轉成 ref 才能繼續與 store 連動。
在 action 中處理 API 請求
action 支援非同步,因此把請求與載入狀態一起放進 store,多個頁面就能共用同一份資料與快取:
// stores/user.js
import { ref } from 'vue';
import { defineStore } from 'pinia';
import axios from 'axios';
export default defineStore('user', () => {
const profile = ref(null);
const isLoading = ref(false);
async function fetchProfile() {
// 已經有資料就不重複請求,達到快取效果
if (profile.value) return profile.value;
isLoading.value = true;
try {
const res = await axios.get('/api/user/profile');
profile.value = res.data;
return profile.value;
} finally {
isLoading.value = false;
}
}
return { profile, isLoading, fetchProfile };
});元件端只要呼叫 fetchProfile() 即可,完全不需要知道網址與載入細節。請求的錯誤處理寫法可參考 Axios 使用,若請求需要帶 Token,則交給 Axios 封裝 的攔截器統一處理。
兩個實用的內建方法
Pinia 在每個 store 上都提供了以 $ 開頭的工具方法,最常用的是這兩個:
// 一次更新多個 state,開發工具中只記錄成一筆操作
userStore.$patch({ name: 'Away', role: 'admin' });
// 把 state 還原成初始值,登出時很好用(限 Option Store)
userStore.$reset();$reset() 只在 Option Store 可用,因為 Pinia 需要 state() 函式才知道初始值。Setup Store 若要相同效果,得自己寫一個 reset action 把各個 ref 設回預設值。
總結
掌握了 Option / Setup Store 的語法差異以及 storeToRefs 的使用時機,您已經避開了 Pinia 最常見的錯誤。選擇適合專案的寫法,能讓您的 Vue 3 狀態管理既優雅又強大。
常見問題
Option Store 與 Setup Store 該選哪一種?
兩者功能相同。習慣 Vue 2 分區塊寫法的人會覺得 Option Store 好讀;需要在 store 中使用 watch、生命週期或組合其他 Composable 時,Setup Store 更自由。同一專案建議統一風格。
為什麼直接解構 store 就失去響應式?
因為 store 是 reactive 物件,解構等於把當下的值複製出來,變數就與原本的來源斷了連結。要保持連動必須用 storeToRefs 把屬性轉成 ref。
storeToRefs 需要把 action 也包進去嗎?
不需要。action 是函式,本來就沒有響應式問題,直接從 store 解構即可。storeToRefs 只處理 state 與 getters。
在 Pinia 裡可以呼叫 API 嗎?
可以,action 支援非同步。把請求與載入狀態寫在 action 裡,元件只要 await 呼叫並顯示結果,多個頁面就能共用同一份資料與快取。
要一次更新多個 state 有更好的寫法嗎?
有,使用 store.$patch 傳入物件或函式即可一次套用多個變更,開發工具中也只會記錄成一筆操作,比逐個賦值更清楚。
為什麼一定要寫 useXxxStore 才取得 store?
因為 store 必須在 Pinia 安裝完成後才能建立。defineStore 只是定義,實際的實體是在元件中呼叫 useXxxStore 時才產生,因此不要在模組最外層就呼叫它。