一、基本使用
1、什么是 Pinia?
Pinia 是 Vue.js 的官方狀態管理庫,是 Vuex 的“升級版”。它專為 Vue 3 和 Composition API 設計,用于管理多個組件之間共享的數據(也叫“全局狀態”)。
2、為什么需要狀態管理庫?
在 Vue 中,多個組件如果需要共享數據(例如登錄用戶信息、購物車內容、權限配置等),僅使用 props 和 emits 會變得非常復雜。這時就需要一個“中心倉庫”來統一管理這些數據 —— 這就是 Pinia 的作用。
舉個例子
假設你有一個購物車組件、一個商品列表組件和一個訂單組件,都需要訪問或修改購物車的內容。這種“共享數據”就是 Pinia 的用武之地。
在沒有 Pinia 的情況下你可能需要這樣做:
-
商品列表通過
emit
告訴父組件加入購物車 -
父組件再傳遞數據給購物車組件
-
數據很容易混亂且難以追蹤
用 Pinia 后,這些組件可以直接訪問全局共享的購物車數據,邏輯清晰,易于維護。
3、 基本使用
1. 配置 Pinia 到 Vue 應用在 main.js 或 main.ts 中引入并注冊:
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'const app = createApp(App)
const pinia = createPinia()app.use(pinia)
app.mount('#app')2. 創建一個 Store(狀態模塊)
你可以把每個業務邏輯寫在一個獨立的 store 文件中,例如:
// stores/counter.js 或 counter.ts
import { defineStore } from 'pinia'export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),getters: {doubleCount: (state) => state.count * 2},actions: {increment() {this.count++}}
})
state: 用來定義數據getters: 類似于計算屬性actions: 用來修改數據(支持異步)
3. 在組件中使用 Store
<script setup>
import { useCounterStore } from '@/stores/counter'const counter = useCounterStore()function addOne() {counter.increment()
}
</script><template><div>當前計數:{{ counter.count }}</div><div>雙倍計數:{{ counter.doubleCount }}</div><button @click="addOne">+1</button>
</template>
二、store
1、什么是 Pinia 的 Store?
在 Pinia 中,store 是用來集中管理狀態、邏輯和數據的地方。它就像是一個“全局的數據容器”,供多個 Vue 組件共享和操作。
每一個 store
都是獨立的模塊,可以包含:
-
state(狀態數據)
-
getters(計算屬性)
-
actions(行為函數)
?Store 是用?defineStore()
?定義的,它的第一個參數要求是一個獨一無二的名字。這個名字?,也被用作?id?,是必須傳入的, Pinia 將用它來連接 store 和 devtools。為了養成習慣性的用法,將返回的函數命名為?use...?是一個符合組合式函數風格的約定。
import { defineStore } from 'pinia'// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同時以 `use` 開頭且以 `Store` 結尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一個參數是你的應用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {// 其他配置...
})
defineStore()
?的第二個參數可接受兩類值:Setup 函數或 Option 對象。
1、Option Store?
與 Vue 的選項式 API 類似,我們也可以傳入一個帶有?state
、actions
?與?getters
?屬性的 Option 對象
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0, name: 'Eduardo' }),getters: {doubleCount: (state) => state.count * 2,},actions: {increment() {this.count++},},
})
你可以認為 state 是 store 的數據 (data),getters 是 store 的計算屬性 (computed),而 actions 則是方法 (methods)。
2、Setup Store?
也存在另一種定義 store 的可用語法。與 Vue 組合式 API 的?setup 函數?相似,我們可以傳入一個函數,該函數定義了一些響應式屬性和方法,并且返回一個帶有我們想暴露出去的屬性和方法的對象。
export const useCounterStore = defineStore('counter', () => {const count = ref(0)const doubleCount = computed(() => count.value * 2)function increment() {count.value++}return { count, doubleCount, increment }
})
在?Setup Store?中:
ref()
?就是?state
?屬性computed()
?就是?getters
function()
?就是?actions
注意,要讓 pinia 正確識別?state
,你必須在 setup store 中返回?state
?的所有屬性。這意味著,你不能在 store 中使用私有屬性。不完整返回會影響?SSR?,開發工具和其他插件的正常運行。?
2、使用 Store?
雖然我們前面定義了一個 store,但在我們使用?<script setup>
?調用?useStore()
(或者使用?setup()
?函數,像所有的組件那樣) 之前,store 實例是不會被創建的:
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在組件中的任意位置訪問 `store` 變量 ?
const store = useCounterStore()
</script>?
請注意,store
?是一個用?reactive
?包裝的對象,這意味著不需要在 getters 后面寫?.value
。就像?setup
?中的?props
?一樣,我們不能對它進行解構:
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'const store = useCounterStore()
// ? 這將不起作用,因為它破壞了響應性
// 這就和直接解構 `props` 一樣
const { name, doubleCount } = store
name // 將始終是 "Eduardo" //
doubleCount // 將始終是 0 //
setTimeout(() => {store.increment()
}, 1000)
// ? 這樣寫是響應式的
// 💡 當然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>
?3、如何從 Pinia 的 store 中 解構變量 的同時 保持響應式
?在 Vue 中,如果你這樣寫:const { name, doubleCount } = useCounterStore()
那么 name
和 doubleCount
會失去響應性,也就是說它們變成了普通變量,不再隨著 store 的狀態變化自動更新頁面。?
?使用 storeToRefs():
import { storeToRefs } from 'pinia'const store = useCounterStore()
const { name, doubleCount } = storeToRefs(store)
?storeToRefs()
會把 store 中的所有響應式屬性(state
和 getters
)轉換成 ref
對象,從而保留響應性。
const { increment } = store
這里可以直接解構 action,因為:
-
actions 本身就是函數
-
它們是綁定過
this
的,不需要ref
包裝 -
所以解構時不會失效,直接拿來調用即可
4、總結
屬性類型 | 是否需要 storeToRefs() | 是否保持響應性 | 是否可以解構 |
---|---|---|---|
state 數據 | ? 需要 | ? | ?(通過 storeToRefs) |
getters | ? 需要 | ? | ?(通過 storeToRefs) |
actions | ? 不需要 | ?(始終) | ?(直接解構) |
三、state?
?在 Pinia 中,state
就是你用來存儲全局數據的地方。它相當于組件中的 data()
,但它可以被多個組件共享。
?例如:
state: () => ({count: 0,user: {name: 'Xia',loggedIn: false}
})
?這段代碼定義了兩個狀態變量:count
和 user
,你可以在任何組件中使用和修改它們。
1、state
的三個關鍵點
1. 必須是一個函數(返回對象)
這是為了確保每個 store 實例有自己的狀態(尤其是在 SSR 環境中)。
state: () => ({count: 0
})
2. 是響應式的
Pinia 內部使用 Vue 的響應式系統(reactivity API)來管理狀態。這意味著當你修改 state
中的數據,使用它的組件會自動更新。
3. 可以直接讀寫
與 Vuex 不同,Pinia 的 state
變量可以直接修改,不需要通過 mutation。例如:
store.count++ // 這是合法的
store.user.name = 'Yang' // 也可以這樣直接改
2、 state高級用法
1. 重置 state($reset()
方法)
作用
-
將
state
恢復為創建時定義的初始值。 -
適合用于退出登錄、清空表單、重置計數器等場景。
使用方式
選項式 API 可直接使用 store.$reset()
。
const store = useStore()
store.$reset()
這個方法會自動調用最初定義的 state() 函數,用返回的對象替換當前的 state。
組合式 API 需要手動實現 $reset()
方法。?
?如果你使用的是 組合式 API(即 defineStore('id', () => {...})
格式),需要自己手動寫一個 $reset()
方法:
export const useCounterStore = defineStore('counter', () => {const count = ref(0)function $reset() {count.value = 0}return { count, $reset }
})
2、$patch()
— 批量或復雜修改 state
作用
-
用于一次性更新多個 state 屬性。
-
避免多次賦值產生性能問題,也方便調試(devtools 會合并記錄)。
-
支持復雜的數組/對象操作。
用法
對象方式(簡潔)
store.$patch({count: store.count + 1,name: 'DIO'
})函數方式(適合復雜修改)
store.$patch((state) => {state.items.push({ id: 1, name: 'shoes' })
})
?$state
— 替換整個 state(實際上是 patch)
作用
-
用于初始化或從服務器、localStorage 恢復 state。
-
雖然看起來是“覆蓋”,但實際上是調用了
$patch()
。
store.$state = { count: 24, name: 'Zhang' }
?3、mapState()
— 把 state 映射為只讀計算屬性
作用
-
用于在選項式 API 的組件中使用 Pinia 的 state。
-
讓
store.count
變成this.count
,方便模板訪問。 -
本質上是一個只讀
computed()
。
computed: {...mapState(useCounterStore, ['count'])
}
?4、mapWritableState()
— 把 state 映射為可寫計算屬性
作用
-
和
mapState()
類似,但支持修改值。 -
適合表單綁定、v-model 等雙向數據綁定場景。
computed: {...mapWritableState(useCounterStore, ['count'])
}
? 你可以在模板中用 v-model="count" 實現響應式輸入。
5、$subscribe()
— 監聽 state 的變化
作用
-
監聽 store 的 state 改變,并觸發回調。
-
常用于持久化(localStorage)、發送日志、自動保存等場景。
-
與 Vue 的
watch()
不同:$subscribe()
是 patch 觸發一次,不是屬性粒度觸發。
store.$subscribe((mutation, state) => {localStorage.setItem('cart', JSON.stringify(state))
})
?加上 { detached: true }
,讓監聽器在組件卸載后仍然有效(適合全局監聽)。
6、watch(pinia.state)
— 監聽整個應用狀態(全局級別)
? 作用
-
用于在應用級別跟蹤所有 store 的狀態。
-
常用于初始化持久化系統或跨模塊狀態變化處理。
watch(pinia.state,(state) => {localStorage.setItem('piniaState', JSON.stringify(state))},{ deep: true }
)
小知識:?
“初始化持久化系統”是指在 Vue 應用中,通過 Pinia 將全局狀態(state)保存到瀏覽器的本地存儲(如 localStorage),并在應用加載時恢復之前保存的狀態,從而實現“斷電重連”、“頁面刷新不丟數據”等功能。
?“跨模塊狀態變化處理”指的是:監聽或響應多個 Pinia store 之間的狀態變動,從而實現它們之間的協調、聯動或同步邏輯。
為什么需要跨模塊狀態處理?
在實際開發中,你通常會為不同功能創建多個 store(模塊化):
Store 模塊 | 功能 |
---|---|
userStore | 管理登錄用戶信息 |
cartStore | 管理購物車內容 |
orderStore | 管理訂單信息 |
uiStore | 控制 UI 狀態(彈窗、loading 等) |
但這些模塊的狀態往往不是孤立的:
-
用戶退出登錄時,需要清空購物車、重置訂單模塊
-
添加商品到購物車后,UI 彈窗需要自動關閉
-
切換語言后,需要讓其他模塊響應更新
四、Getter?
1、什么是 Getter?
在 Pinia 中,getter
就是計算屬性(computed),用于從 state
中派生出一些數據,例如格式化、計算總和、過濾等。
它的作用就像:
-
Vue 組件中的
computed
-
Vuex 中的
getter
2、Getter 的作用
主要功能 | 舉例說明 |
---|---|
派生出新值 | 例如 items.length 、count * 2 、是否登錄 |
基于 state 但不會直接修改 state | 保證“只讀邏輯” |
提高代碼可讀性 | 通過 getter 抽象邏輯,例如 isAdmin |
性能優化 | 只有依賴的值變化時才重新計算(和 computed 一樣) |
?Getter 的定義方式
在選項式 API中(推薦初學者)
export const useCounterStore = defineStore('counter', {state: () => ({count: 2}),getters: {doubleCount: (state) => state.count * 2,isEven: (state) => state.count % 2 === 0}
})在組合式 API中(進階)
export const useCounterStore = defineStore('counter', () => {const count = ref(2)const doubleCount = computed(() => count.value * 2)return { count, doubleCount }
})
?Getter 的使用方式
在組件中直接訪問(不需要調用)
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
</script><template><p>原始:{{ counter.count }}</p><p>雙倍:{{ counter.doubleCount }}</p>
</template>
? 注意:getter 就像計算屬性,是自動響應式的,不需要加括號。?? Getter 可以訪問其他 state、getter
getters: {isEven: (state) => state.count % 2 === 0,label: (state, getters) => getters.isEven ? '偶數' : '奇數'
}
3、Getter 是只讀的
你不能直接修改 getter 的返回值:
?store.doubleCount = 10 // ? 不允許,會報錯
?如果你需要修改值,請通過 action 或直接改 state。
?1. 訪問其他 Getter(組合 Getter)
?當你想讓一個 getter 基于另一個 getter 的結果繼續計算,可以直接通過 this
訪問其他 getter。
export const useCounterStore = defineStore('counter', {state: () => ({ count: 0 }),getters: {doubleCount(state) {return state.count * 2},doubleCountPlusOne(): number {return this.doubleCount + 1}}
})
💡 重點說明
this.doubleCount 是另一個 getter
如果你用 TypeScript,必須指定返回值類型,如 : number
2. 向 Getter 傳遞參數(返回一個函數)
? 錯誤方式(不能這樣做)
// 不支持:getUserById(id) { ... }正確方式:返回一個函數
export const useUserListStore = defineStore('userList', {state: () => ({users: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]}),getters: {getUserById: (state) => {return (userId) => state.users.find((user) => user.id === userId)}}
})
注意
-
返回函數的 getter 不會被緩存(不像 computed),每次調用都重新執行
-
更適合用于 過濾、查找 等需要參數的場景
?3. 緩存變體:先篩選再返回函數
getActiveUserById(state) {const activeUsers = state.users.filter((u) => u.active)return (id) => activeUsers.find((u) => u.id === id)
}
適合場景:
當你要多次按 id 查詢某個子集(如活躍用戶)
手動做了第一步過濾以節省后續運算
?4. 訪問其他 Store 的 Getter
import { useOtherStore } from './other-store'export const useStore = defineStore('main', {state: () => ({ localData: 10 }),getters: {otherGetter(state) {const otherStore = useOtherStore()return state.localData + otherStore.doubleValue // 使用別的 store 的 getter}}
})
你可以在 getter 中隨意使用其他 store,就像在組件中那樣要確保其他 store 已經定義好盡量避免循環依賴(Store A 調用 B,B 又回頭調用 A)
?五、Action
?Action 相當于組件中的?method。它們可以通過?defineStore()
?中的?actions
?屬性來定義,并且它們也是定義業務邏輯的完美選擇。
?類似?getter,action 也可通過?this
?訪問整個 store 實例,并支持完整的類型標注(以及自動補全?)。不同的是,action
?可以是異步的,你可以在它們里面?await
?調用任何 API,以及其他 action!
?1、定義
1. 使用選項式 API 定義 Store
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {state: () => ({count: 0}),actions: {increment() {this.count++}}
})
? 說明:
state: 返回一個對象,定義響應式數據 actions: 包含可以修改 state 的方法
這是 Pinia 的 選項式 API 寫法,類似 Vue 組件的 data、methods🧩 2. 在組件中使用 store(有兩種寫法)
? 使用 setup()(組合式 API 風格)
import { useCounterStore } from '@/stores/counter'
export default defineComponent({setup() {const counterStore = useCounterStore()return { counterStore }},methods: {incrementAndPrint() {this.counterStore.increment()console.log('New Count:', this.counterStore.count)}}
})
📌 特點:
雖然這是在選項式組件中,但使用了 setup(),所以不需要 mapState() 或 mapActions() 映射
store 的屬性都掛在 this.counterStore 上
? 不使用 setup(),使用 mapActions() 輔助函數
import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counter'export default {methods: {...mapActions(useCounterStore, ['increment']),...mapActions(useCounterStore, { myOwnName: 'increment' })}
}特點:將 store 中的 action 映射為當前組件的 method
this.increment() 實際調用的是 store.increment()
如果你不使用 setup(),這是官方推薦的寫法
2. 訂閱 Action 的執行:$onAction()
? 作用:
$onAction()
允許你攔截 action 的執行過程,記錄日志、添加監控、處理錯誤等。
const unsubscribe = store.$onAction(({ name, args, after, onError }) => {const start = Date.now()console.log(`Start "${name}" with args:`, args)after((result) => {console.log(`Finished "${name}" in ${Date.now() - start}ms. Result:`, result)})onError((err) => {console.warn(`Failed "${name}" in ${Date.now() - start}ms. Error:`, err)})
})
📌 回調參數說明:
參數 | 說明 |
---|---|
name | 當前執行的 action 名稱 |
args | 調用該 action 時傳入的參數 |
after(fn) | 在 action 成功執行后觸發的回調 |
onError(fn) | 在 action 執行失敗或拋錯時觸發的回調 |
3. 解綁監聽器(防止內存泄露)
?unsubscribe() // 手動移除訂閱器
? 默認行為:
-
如果 store 是在組件的
setup()
中創建的,$onAction()
會自動在組件卸載時移除監聽器。
? 永久監聽(不隨組件銷毀):
?store.$onAction(callback, true) // 第二個參數為 true
適用于:
-
全局日志系統
-
性能監控
-
狀態操作歷史記錄
?六、插件
1、什么是 Pinia 插件?
Pinia 插件是用來擴展所有 store 的功能的工具。它們可以:
-
添加默認屬性
-
注入共享方法
-
實現狀態持久化、本地緩存、日志記錄等功能
可以把插件理解為:給每個 Store 加“外掛”功能,類似 Vue 插件或 Vuex 插件。
插件 = 給每個 store 加功能的小外掛。
想象一下你有很多個 store,就像很多個員工。你想讓每個員工都多一個新技能,比如:
-
會自動保存工作成果(= 自動把 state 保存到 localStorage)
-
每次干活都寫日志(= action 調用時打印日志)
-
都能說一句“你好老板”(= 添加一個公共方法)
你不用每次都手動寫這段邏輯,而是通過 插件,一次性注入到所有 store 里,就像給每個員工配發一個新“工具包”。
2、插件能做什么?
Pinia 插件可以用來實現以下場景:
功能 | 示例 |
---|---|
狀態持久化 | 保存 store 到 localStorage |
自動記錄操作日志 | 記錄每次 action 調用的時間、參數 |
注入公共方法 | 如 $toast() 、$confirm() |
初始化共享狀態 | 比如全局主題、設置項 |
插件封裝業務邏輯 | 例如 API 請求封裝、授權檢測 |
?Pinia 插件是一個函數,可以選擇性地返回要添加到 store 的屬性。它接收一個可選參數,即?context。
export function myPiniaPlugin(context) {context.pinia // 用 `createPinia()` 創建的 pinia。context.app // 用 `createApp()` 創建的當前應用(僅 Vue 3)。context.store // 該插件想擴展的 storecontext.options // 定義傳給 `defineStore()` 的 store 的可選對象。// ...
}
然后用?pinia.use()
?將這個函數傳給?pinia
:
pinia.use(myPiniaPlugin)
插件只會應用于在?pinia
?傳遞給應用后創建的 store,否則它們不會生效。
3、如何使用插件?
? 注冊插件到 Pinia 實例
// main.js
import { createPinia } from 'pinia'
import piniaPlugin from './myPlugin'const pinia = createPinia()
pinia.use(piniaPlugin)
? 插件的基本結構
// myPlugin.js
export default function myPlugin({ store }) {// 添加一個自定義屬性store.hello = 'world'// 添加一個共享方法store.sayHello = () => {console.log(`Hello from ${store.$id}`)}
}
使用后,你可以在任意 store 中訪問:
const store = useMyStore()
store.sayHello() // 打印:Hello from myStore
4、插件能做的幾件事(舉例)
插件功能 | 作用說明 | 類比 |
---|---|---|
自動保存 state 到本地 | 頁面刷新后還能恢復數據 | 自動保存工作成果 |
打印日志 | 每次調用 action 時記錄操作 | 打卡上下班 |
增加自定義方法 | 比如 $toast() 彈窗提示 | 提供快捷按鈕 |
修改 store 行為 | 比如封裝通用校驗 | 定制化員工行為 |
5、插件和 store 有什么區別?
Store 是什么? | 插件是干嘛的? |
---|---|
管理狀態(數據)和操作(actions) | 擴展每個 store 的功能 |
每個業務寫一個 | 全局使用一次,所有 store 都能用 |
比如 userStore , cartStore | 比如“保存功能”、“日志功能” |
6、插件的真正價值
插件的核心作用是:統一增強所有 store 的功能,而不是在每個 store 里重復寫。
舉個例子:
如果你希望所有模塊的狀態都能自動保存到本地(比如購物車、用戶設置等),你肯定不想在每個 store 里寫一遍 localStorage.setItem()
。
所以,你就寫一個插件,讓每個 store 自動保存它自己的數據,只要注冊一次,全局生效。
?