《Pinia 從入門到精通》Vue 3 官方狀態管理 – 基礎入門篇
《Pinia 從入門到精通》Vue 3 官方狀態管理 – 進階使用篇
《Pinia 從入門到精通》Vue 3 官方狀態管理 – 插件擴展篇
目錄
- Store 的模塊化設計
- 4.1 多模塊結構設計
- ? 推薦目錄結構(中大型項目)
- 4.2 定義獨立模塊 Store
- 4.3 在組件中組合多個 Store
- 4.4 跨模塊調用(解耦調用)
- 4.5 模塊化 Store 的命名約定
- 4.6 所有 Store 統一導出(可選)
- ? 小結
- 類型系統集成(TypeScript 支持)
- 5.1 Store 的類型推導基礎
- 5.2 自定義類型(推薦)
- ? 定義接口
- ? 使用泛型定義 Store
- 5.3 Setup Store(組合式寫法)中的類型支持
- 5.4 類型提示與 IDE 自動補全效果
- 5.5 類型約束下的好處
- ? 小結
- 持久化存儲與插件機制
- 6.1 什么是 Pinia 插件?
- 6.2 狀態持久化插件:`pinia-plugin-persistedstate`
- ? 安裝
- ? 注冊插件
- 6.3 使用持久化配置
- 6.4 自定義持久化策略
- 6.5 多模塊下持久化組合
- 6.6 自定義插件機制(進階)
- 6.7 生命周期鉤子:`$subscribe` & `$onAction`
- 1. `$subscribe` —— 監聽狀態變化
- 2. `$onAction` —— 監聽 Action 執行
- ? 小結
- 最佳實踐總結與項目結構規范化設計
- 7.1 推薦項目結構
- 7.2 Store 命名規范
- 7.3 狀態設計原則
- 7.4 Store 類型系統統一
- 7.5 持久化策略標準化
- 7.6 自動化導出 Store
- 7.7 組合邏輯封裝:`useXXXLogic`
- ? 實戰應用:多頁面應用的 Store 模型設計范式
- ? 統一 Store 模板(可直接復制)
- ? 結語:Pinia 最佳實踐總覽圖
Store 的模塊化設計
隨著項目規模擴大,單一 Store 會迅速膨脹,導致維護困難。
我們將從實際項目結構出發,講解如何構建多模塊 Store,支持清晰組織、職責分離和可維護性。
Pinia 提供了天然模塊化的設計,每一個 Store 就是一個模塊,天生支持按需加載、組合調用。
4.1 多模塊結構設計
? 推薦目錄結構(中大型項目)
src/
├── stores/
│ ├── index.ts # 導出所有 store(可選)
│ ├── user.ts # 用戶模塊
│ ├── auth.ts # 登錄認證模塊
│ ├── cart.ts # 購物車模塊
│ └── product.ts # 商品模塊
每個模塊都用 defineStore
定義自己的狀態、邏輯、計算屬性,保持內聚。
4.2 定義獨立模塊 Store
// stores/user.ts
import { defineStore } from 'pinia'export const useUserStore = defineStore('user', {state: () => ({name: '',email: ''}),actions: {setUser(name: string, email: string) {this.name = namethis.email = email}}
})
// stores/cart.ts
import { defineStore } from 'pinia'export const useCartStore = defineStore('cart', {state: () => ({items: [] as { id: number; name: string; qty: number }[]}),getters: {totalItems: (state) => state.items.reduce((sum, item) => sum + item.qty, 0)},actions: {addItem(item: { id: number; name: string; qty: number }) {this.items.push(item)}}
})
4.3 在組件中組合多個 Store
<script setup lang="ts">
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'const userStore = useUserStore()
const cartStore = useCartStore()function checkout() {console.log(`${userStore.name} 正在購買 ${cartStore.totalItems} 件商品`)
}
</script>
4.4 跨模塊調用(解耦調用)
Pinia 中不同 Store 可相互獨立調用,而無需手動注入依賴:
// stores/order.ts
import { defineStore } from 'pinia'
import { useUserStore } from './user'export const useOrderStore = defineStore('order', {actions: {submitOrder() {const userStore = useUserStore()console.log(`下單用戶:${userStore.name}`)// ...業務邏輯}}
})
注意:調用其他 Store 必須在 action 中動態獲取,而不能在 Store 頂層直接引入(避免依賴環問題)。
4.5 模塊化 Store 的命名約定
內容 | 命名規則 | 示例 |
---|---|---|
Store 函數 | useXxxStore | useUserStore |
文件名 | 模塊名小寫 | user.ts |
Store ID | 與函數名一致的小寫形式 | 'user' |
命名空間 | 使用 id 區分,無需嵌套定義 | 所有 Store 自動隔離 |
4.6 所有 Store 統一導出(可選)
你可以在 stores/index.ts
中統一導出,方便使用:
// stores/index.ts
export * from './user'
export * from './cart'
export * from './order'
// 使用
import { useUserStore, useCartStore } from '@/stores'
? 小結
模塊化是構建大型 Vue 應用的核心策略,Pinia 以其函數式 Store 和天然隔離的 Store ID 設計,使模塊化變得:
- 更加清晰(每個模塊職責單一)
- 更易維護(按需加載,互不干擾)
- 更易測試(每個 Store 獨立可測試)
類型系統集成(TypeScript 支持)
Pinia 天生支持 TypeScript,基于其函數式 API 和明確的聲明結構,使得類型推導更加自然、精準。
5.1 Store 的類型推導基礎
最基礎的 defineStore
形式在 TS 中已經具備類型提示能力:
export const useCounterStore = defineStore('counter', {state: () => ({count: 0,name: 'Counter Module'}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
此時使用該 Store 時會自動獲得類型提示:
const store = useCounterStore()
store.count // number ?
store.increment() // 自動補全 ?
5.2 自定義類型(推薦)
雖然大多數場景下可自動推導,但我們仍推薦使用接口顯式定義 state
、getters
、actions
類型以獲得更強可維護性。
? 定義接口
// types/store.d.ts
export interface CounterState {count: numbername: string
}export interface CounterGetters {doubleCount(state: CounterState): number
}export interface CounterActions {increment(): void
}
? 使用泛型定義 Store
import { defineStore, StoreDefinition } from 'pinia'
import type { CounterState, CounterActions, CounterGetters } from '@/types/store'export const useCounterStore: StoreDefinition<'counter', CounterState, CounterGetters, CounterActions> = defineStore('counter', {state: (): CounterState => ({count: 0,name: 'Counter Store'}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
5.3 Setup Store(組合式寫法)中的類型支持
對于復雜邏輯,我們可以使用組合式寫法,即 setup
形式的 Store,TS 類型控制更靈活。
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'export const useUserStore = defineStore('user', () => {const name = ref('Alice')const age = ref(25)const summary = computed(() => `${name.value}(${age.value}歲)`)function setName(newName: string) {name.value = newName}return {name,age,summary,setName}
})
此種寫法中,Pinia 能自動推導返回值類型,無需額外泛型,只要你在返回時顯式寫出 ref
/ computed
。
5.4 類型提示與 IDE 自動補全效果
- State 屬性:可識別為
ref<number>
/ref<string>
等 - Getter 屬性:識別為
ComputedRef
- Action 方法:自動推導參數和返回值類型
舉個例子:
const userStore = useUserStore()
userStore.age.value // number ?
userStore.summary.value // string ?
userStore.setName('Bob') // ?
5.5 類型約束下的好處
特性 | 說明 |
---|---|
自動補全 | 所有屬性、方法可自動提示,無需手動查找 |
靜態校驗 | 錯誤屬性/參數在編譯期即可發現,避免運行時異常 |
支持重構 | 改名、移動屬性時 IDE 可自動跟蹤更新引用 |
接口復用 | 同一份接口可復用在組件、接口、后端通訊中 |
? 小結
Pinia 在 TypeScript 項目中具備一流的類型體驗:
- 推薦用接口明確 State / Action / Getter 結構
- 可選使用組合式寫法提升靈活性與組合能力
- 強類型推導貫穿編寫、使用、調試各個環節
持久化存儲與插件機制
本節將深入講解如何使用 Pinia 插件 實現 Store 狀態的持久化存儲,以及自定義擴展 Store 功能。
在實際項目中,我們常常需要:
- 保持用戶登錄信息,即使刷新頁面也不丟失
- 記住用戶的主題偏好、語言設置
- 在多個 Tab 頁面之間共享狀態
Pinia 原生支持插件機制,非常適合用于這些擴展場景。
6.1 什么是 Pinia 插件?
插件是一個函數,在每個 Store 實例創建時執行。你可以通過插件:
- 添加全局狀態
- 劫持/監聽 Store 生命周期
- 自動同步狀態到 localStorage / sessionStorage
- 注入外部依賴(如 Axios)
6.2 狀態持久化插件:pinia-plugin-persistedstate
最常用的第三方插件是 pinia-plugin-persistedstate
,可自動將狀態持久化到本地存儲。
? 安裝
npm install pinia-plugin-persistedstate
? 注冊插件
// main.ts
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)app.use(pinia)
6.3 使用持久化配置
只需在 defineStore
中增加 persist
配置:
// stores/user.ts
export const useUserStore = defineStore('user', {state: () => ({token: '',theme: 'light'}),persist: true // 默認使用 localStorage
})
刷新后依然保留 token 和主題偏好。
6.4 自定義持久化策略
persist: {enabled: true,strategies: [{key: 'user-token',storage: sessionStorage,paths: ['token'] // 只存 token}]
}
key
:本地存儲的鍵名storage
:可選localStorage
或sessionStorage
paths
:只持久化指定字段
6.5 多模塊下持久化組合
每個模塊 Store 都可獨立配置 persist
,互不影響:
// user.ts => 存 token 到 sessionStorage
persist: {storage: sessionStorage,paths: ['token']
}// settings.ts => 存 theme 到 localStorage
persist: {storage: localStorage,paths: ['theme']
}
6.6 自定義插件機制(進階)
你可以自定義自己的插件來擴展 Store 功能:
// plugins/logger.ts
import type { PiniaPluginContext } from 'pinia'export function loggerPlugin({ store }: PiniaPluginContext) {store.$subscribe((mutation, state) => {console.log(`[${mutation.storeId}]`, mutation.type, mutation.events)})
}
注冊插件:
pinia.use(loggerPlugin)
這樣每次狀態改變都會打印日志。
6.7 生命周期鉤子:$subscribe
& $onAction
Pinia 提供兩種原生生命周期鉤子:
1. $subscribe
—— 監聽狀態變化
const userStore = useUserStore()userStore.$subscribe((mutation, state) => {console.log('狀態發生變化:', mutation, state)
})
2. $onAction
—— 監聽 Action 執行
userStore.$onAction(({ name, args, after, onError }) => {console.log(`Action 被調用: ${name}`, args)after(() => console.log(`${name} 執行成功`))onError((err) => console.error(`${name} 報錯`, err))
})
非常適合做埋點、錯誤日志等邏輯。
? 小結
Pinia 插件機制極為強大和靈活:
功能類型 | 工具 | 用途描述 |
---|---|---|
本地持久化 | pinia-plugin-persistedstate | 自動同步狀態到 Storage |
狀態監聽 | $subscribe | 跟蹤 State 的變化 |
行為監聽 | $onAction | 跟蹤 Action 的執行情況 |
自定義擴展 | 插件函數 | 注入工具、處理副作用、封裝邏輯等 |
最佳實踐總結與項目結構規范化設計
將基于前面內容,系統梳理一套企業級 Pinia 狀態管理的最佳實踐,從模塊設計、命名規范、狀態解耦、持久化、類型安全等多個維度,構建一個清晰、穩定、可維護、易擴展的 Store 架構體系。
7.1 推薦項目結構
src/
├── stores/ # 所有 Pinia Store 模塊
│ ├── user.ts # 用戶相關狀態
│ ├── auth.ts # 權限與登錄認證
│ ├── ui.ts # UI 狀態(如 sidebar)
│ ├── settings.ts # 全局設置項
│ └── index.ts # 自動導出所有 Store
├── types/ # Store 類型定義
│ └── store.d.ts
├── plugins/ # 自定義插件(如 router 注入、日志等)
├── composables/ # 組合邏輯封裝,可配合 Store 使用
7.2 Store 命名規范
類型 | 命名建議 | 示例 |
---|---|---|
Store 名稱 | useXxxStore | useUserStore |
ID(storeId) | 模塊名小寫 | user , auth , ui |
文件名 | 模塊名小寫 | user.ts , auth.ts |
命名統一,利于團隊協作和自動化生成。
7.3 狀態設計原則
- 一個模塊職責單一,避免巨型 Store
- 狀態最小化:只存 UI 需要的狀態,不要存派生數據(放 getters)
- 與組件無關的數據放 Store,臨時數據放組件
- 使用
ref
,computed
,watch
配合使用 Store 提高響應性控制
7.4 Store 類型系統統一
統一定義 Store 的 state
, getters
, actions
類型接口:
// types/store.d.ts
export interface UserState { name: string; age: number }
export interface UserActions { login(): void; logout(): void }
export interface UserGetters { isAdult: boolean }
結合 StoreDefinition
:
export const useUserStore: StoreDefinition<'user', UserState, UserGetters, UserActions> =defineStore('user', {state: (): UserState => ({ ... }),...})
優點:
- 強類型保障
- 便于重構
- 編輯器智能提示清晰
7.5 持久化策略標準化
- token、用戶信息 → 存到
sessionStorage
(瀏覽器關閉清空) - 用戶偏好、主題設置 → 存到
localStorage
(長期保存) - 配置統一封裝成策略常量:
const localPersist = {storage: localStorage,paths: ['theme', 'language']
}
7.6 自動化導出 Store
// stores/index.ts
export * from './user'
export * from './auth'
export * from './settings'
支持模塊自動導入:
import { useUserStore, useAuthStore } from '@/stores'
7.7 組合邏輯封裝:useXXXLogic
業務邏輯不要堆在組件里,應該封裝成組合邏輯:
// composables/useLogin.ts
export function useLogin() {const userStore = useUserStore()const doLogin = async (formData) => {await userStore.login(formData)router.push('/dashboard')}return { doLogin }
}
- 提高復用性
- 分離 UI 與業務邏輯
- 組件更輕盈可測試
? 實戰應用:多頁面應用的 Store 模型設計范式
模塊名稱 | 典型狀態字段 | 持久化 | 常駐內存 | 是否解耦 UI |
---|---|---|---|---|
user | token , info | ? | ? | ? |
ui | sidebar , theme | ? | ? | ? |
auth | roles , routes | ? | ? | ? |
search | keyword , filters | ? | ? | ?(頁面級) |
? 統一 Store 模板(可直接復制)
// stores/xxx.ts
import { defineStore } from 'pinia'
import type { XxxState } from '@/types/store'export const useXxxStore = defineStore('xxx', {state: (): XxxState => ({ ... }),getters: {...},actions: {...},persist: {enabled: true,strategies: [...]}
})
? 結語:Pinia 最佳實踐總覽圖
+-------------------------------+
| 模塊劃分清晰 |
| ↓ |
| 單一職責 |
| ↓ |
| 類型定義接口統一 |
| ↓ |
| Plugin 封裝 & 邏輯抽離 |
| ↓ |
| 狀態最小化 & 可持久化 |
| ↓ |
| 與 Router / API / UI 解耦 |
+-------------------------------+
Pinia 的設計理念是簡單、透明、類型友好。配合組合式 API,它不僅可以替代 Vuex,還能幫助我們構建更現代、更可控、更高效的前端架構。