引言

Vue 應用規模一大,狀態管理就會開始讓人頭痛。Pinia 是 Vue 官方推薦的狀態管理函式庫,設計目標是型別安全、直觀,且不需要 Vuex 那套繁瑣的 mutation 流程。本文涵蓋 Pinia 的核心概念、進階特性與常見實踐,從安裝到插件系統都會走一遍。

什麼是Pinia?

Pinia 是新一代的 Vue 狀態管理函式庫,定位上取代 Vuex。由 Vue 核心團隊成員 Eduardo San Martin Morote 建立,在 Vue 3 中成為官方推薦的狀態管理方案。

Pinia的主要特性:

  1. 直觀且簡潔的 API
  2. 完整的 TypeScript 支援
  3. 支援 Vue 2 和 Vue 3(Pinia v2;v3 起僅支援 Vue 3)
  4. 極小的套件大小
  5. 支援多個 Store
  6. 支援熱模組替換(HMR)
  7. 支援 Vue DevTools
  8. 插件系統可擴充

安裝和設置

1
npm install pinia

在 Vue 應用中引入 Pinia:

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

Pinia的核心概念

Store

Store 是 Pinia 的核心,包含狀態(state)、getter 和 action。

建立一個基本的 Store:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})

組合式API風格的Store

Pinia 也支援組合式 API 風格定義 Store,TypeScript 推斷會更完整:

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double Count: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>

<script setup>
import { useCounterStore } from '@/stores/counter'

const counter = useCounterStore()
</script>

注意:若要解構 store 裡的響應式屬性,需搭配 storeToRefs,直接解構會失去響應性:

1
2
import { storeToRefs } from 'pinia'
const { count, doubleCount } = storeToRefs(counter)

Pinia的高級特性

1. 訂閱狀態變化

1
2
3
4
5
6
const unsubscribe = someStore.$subscribe((mutation, state) => {
// 每次 state 變化時觸發
console.log(mutation.type)
console.log(mutation.storeId)
console.log(mutation.payload)
})

2. 持久化

搭配 pinia-plugin-persistedstate 可以把 store 狀態寫入 localStorage:

1
2
3
4
5
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(createPersistedState())

3. 插件系統

Pinia 的插件接收每個 store 實例,可以在初始化時注入屬性或方法:

1
2
3
4
pinia.use(({ store }) => {
store.customValue = 'Hello'
store.customMethod = () => console.log('Custom method')
})

與Vuex的比較

Pinia 相比 Vuex 的主要差異:

  1. 不需要 mutation,action 直接修改狀態
  2. TypeScript 支援更完整,不需要額外的型別包裝
  3. 不需要嵌套模組,每個 store 獨立
  4. 套件更輕量

最佳實踐

  1. 按功能或業務領域拆分 store,避免一個 store 管所有狀態
  2. 組合式 API 風格對 TypeScript 推斷更友善,大型專案建議優先採用
  3. 需要跨元件共用的衍生狀態放 getter,副作用放 action
  4. 善用插件系統處理持久化、logging 等橫切關注點

結論

Pinia 把全域狀態管理的概念直接對齊 Vue 3 的組合式 API,上手成本低、型別推斷完整。對於已經在用 Vue 3 的專案來說,沒有特別理由繼續留在 Vuex。

參考資源