Skip to main content

Vue3 - Pinia 管理資料

Why Pinia?

雖然 Vue 本身有 props, emit, provide/inject 等機制可以在父子等直系血親元件中進行資料傳遞,但是一旦涉及親屬關係過遠 (ex: 父元件的父元件的父元件...) 或是兩個元件之間根本沒親緣關係以及其他關係複雜的資料流,Vue 本身提供的資料傳遞機制就會變得沒那麼理想 (甚至可以說是會讓 code 成為一團毛線球)。
Pinia 的優點就是把這些複數元件會用到的狀態統一給抽出來由 store 來管理,有了一個單一的狀態管理中心,元件就只需要跟 store 溝通,可以規避複數元件產生的龐大又雜亂的資料流。


在專案中引入 Pinia

當開始建立一個 Vue 專案,系統其實就會詢問是否要使用 Pinia 來管理狀態。若選擇使用,系統就會自動在 /src 資料夾下建立 stores 資料夾,用來放置狀態管理的檔案。
同時,Pinia 也會自動在 main.js 檔案中 import,讓整個專案都可以使用 Pinia。

import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)

app.use(createPinia())
app.mount('#app')
info

對於已經存在的 Vue 專案若想要採用 Pinia 來管理狀態,可以透過 npm 或 yarn 來安裝 Pinia。
安裝完成後,需要手動修改 main.jsmain.ts (視專案設定而定) 來 import 並啟用 Pinia。

npm install pinia

store 又是什麼?

info

Pinia 的 store 其實有 options store 跟 setup store 兩種。這裡只紀錄 setup store 的用法,因為它最貼近 vue3 <script setup> 的寫法。

在新的專案中,系統會自動在 /src/stores 資料夾下建立一個 counter.js 檔案作為 store 的範例。
store 是 Pinia 中用來存放狀態和邏輯的地方,可以存取的內容包含資料 (refreactive)、計算屬性 (computed) 和方法 (method),且可以被全域存取。

info

對那些使用 options store 的用戶,如果要轉 setup store,Pinia 官方有說:

ref() 就是 state 属性
computed() 就是 getters
function() 就是 actions

要建立一個 store,首先要從 pinia 中 import defineStore
通常 store 的命名會習慣以 use 開頭,接著是 store 的主要用途,最後以 Store 結尾,ex: useCounterStore 或是登入常會使用的 useAuthStore

defineStore 接受兩個參數:第一個是 store 的 ID,可以是名稱去掉 use 前綴;第二個是一個箭頭函數,裡面包含要存放的資料或邏輯。以下為範例:

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}

return { count, doubleCount, increment }
})

在元件中使用 store

要存取 store 內的資料或邏輯,可以直接在元件中 import 所需的 store,並將其內容指派給一個變數 (通常以 Store 結尾,如 xxStore)。
之後,這個變數就可以在元件中使用、存取 xxStore 中包含的資料或邏輯。

App.vue
<script setup>
import {useCounterStore} from '@/stores/counter'
const store = useCounterStore()
</script>

<template>
<h1>{{ store.count }}</h1>
<button @click="store.increment">Add</button>
</template>

store 資料的解構

當需要使用 store 中的方法時,可以使用直接的解構方式。
但是對於響應式資料 (包括 computed) 則需要使用 storeToRefs 來提取,以保持其響應性。
以下是官方文件中的範例:

App.vue
<script setup>
import { storeToRefs } from 'pinia'
import {useCounterStore} from '@/stores/counter'

const store = useCounterStore()

<!-- 響應式資料的解構 -->
const {count, doubleCount} = storeToRefs(store)

<!-- method 的解構 -->
const {increment} = store
</script>

<template>
<h1>{{ count }}</h1>
<button @click="increment">Add</button>
</template>

重置 store 中的狀態

在 store 中定義一個 $reset 方法,可以讓 store 回復到初始狀態。這樣就可以輕鬆地將 store 重置為初始狀態。

Counter.js
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}

// add a reset method
const $reset = () => count.value = 0

return { count, doubleCount, increment, $reset }
})
App.vue
<script setup>
import {useCounterStore} from '@/stores/counter'

const store = useCounterStore()
</script>

<template>
<h1>{{ store.count }}</h1>
<button @click="store.increment">Add</button>
<button @click="store.$reset">Reset to 0</button>
</template>

修改狀態 (即 ref or reactive)

在我 2025 因為公司新開專案第一次引入 setup store 的寫法,我又回來重新看了次 Pinia 的官方文件,發現了一個超神奇的更新。
以前官方會說:「直接修改狀態是最直接的方式,但是這樣做會繞過 Vue 的響應式系統,可能導致 UI 不會立即反映變化。」
但現在這些段落在官方文件中直接消失了,官方甚至現在開宗明義直接說:「預設情況下,你可以透過store實例存取state,直接對其進行讀寫。」
酷,所以我回來修改一下這篇筆記 www

除了直接修改,實務上因為對狀態的操作較為複雜,比如需要一堆判斷式之類的,所以通常會寫成函式來修改狀態,ex:

<button @click="store.increment()">Increase</button>

另外官方還提供了一種特殊的方法 $patch,說是可以用來批量更新狀態。但老實說 2025 了我還是幾乎沒看到有人用過這個方法...
但是齁,實務上這種大量修改通常伴隨複雜邏輯,會直接用 method 處理,所以 $patch 這裡就不贅述了。


監聽狀態的變化

雖然也可以使用 watch 來監聽狀態的變化,但是 Pinia 提供了自己的方法來達到這個目的,叫做 $subscribe

$subscribe 方法接受兩個參數:一個是 mutation,另一個是要監聽的對象。mutation 印出來會有三個主要屬性:

  1. type:表示資料變更的方式。
  2. storeId:識別當前的 store。
  3. events:詳細說明狀態中的特定資料變更。

這些狀態變更的資訊可以幫助開發者更好地了解狀態的變化,並且可以更好地進行狀態管理。
(雖然大家還是都用 watch...)

$subscribe
store.$subscribe((mutation, state) =>{
console.log('Something changed!')
}, { detached: true })
watch
watch(store, state =>{
console.log(`watch here`, state)
}, {deep : true})

Notice

這是一點小備註,算是一個不小心就可能做錯,但發現好像無事發生,但官方就是說這樣可能出 bug 的事情。

要讓 pinia 正確識別 state,你必須在 setup store 中傳回 state 的所有屬性。這意味著,你不能在 store 使用私有屬性。不完整回傳會影響 SSR,開發工具和其他外掛程式的正常運作。

上述為官方原文,在 setup store 中設定了哪些 refcomputedmethod,通通把它 return 拋出來就對了。


Reference

  1. Pinia
Buy Me A Coffee