為什么你應該使用 Pinia?
Pinia 是 Vue 的專屬狀態(tài)管理庫,它允許你跨組件或頁面共享狀態(tài)。如果你熟悉組合式 API 的話,你可能會認為可以通過一行簡單的 export const state = reactive({}) 來共享一個全局狀態(tài)。對于單頁應用來說確實可以,但如果應用在服務器端渲染,這可能會使你的應用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型單頁應用中,你也可以獲得如下功能:
- Devtools 支持追蹤 actions、mutations 的時間線在組件中展示它們所用到的 Store讓調(diào)試更容易的 Time travel
- 熱更新不必重載頁面即可修改 Store開發(fā)時可保持當前的 State
- 插件:可通過插件擴展 Pinia 功能
- 為 JS 開發(fā)者提供適當?shù)?TypeScript 支持以及自動補全功能。
- 支持服務端渲染
為什么取名 Pinia?
?
Pinia (發(fā)音為 /pi?nj?/,類似英文中的 “peenya”) 是最接近有效包名 pi?a (西班牙語中的 pineapple,即“菠蘿”) 的詞。 菠蘿花實際上是一組各自獨立的花朵,它們結合在一起,由此形成一個多重的水果。 與 Store 類似,每一個都是獨立誕生的,但最終它們都是相互聯(lián)系的。 它(菠蘿)也是一種原產(chǎn)于南美洲的美味熱帶水果。
對比 Vuex
?
Pinia 起源于一次探索 Vuex 下一個迭代的實驗,因此結合了 Vuex 5 核心團隊討論中的許多想法。最后,我們意識到 Pinia 已經(jīng)實現(xiàn)了我們在 Vuex 5 中想要的大部分功能,所以決定將其作為新的推薦方案來代替 Vuex。
與 Vuex 相比,Pinia 不僅提供了一個更簡單的 API,也提供了符合組合式 API 風格的 API,最重要的是,搭配 TypeScript 一起使用時有非??煽康念愋屯茢嘀С?。
RFC?
最初,Pinia 沒有經(jīng)過任何 RFC 的流程。我基于自己開發(fā)應用的經(jīng)驗,同時通過閱讀其他人的代碼,為使用 Pinia 的用戶工作,以及在 Discord 上回答問題等方式驗證了一些想法。 這些經(jīng)歷使我產(chǎn)出了這樣一個可用的解決方案,并適應了各種場景和應用規(guī)模。我會一直在保持其核心 API 不變的情況下發(fā)布新版本,同時不斷優(yōu)化本庫。
現(xiàn)在 Pinia 已經(jīng)成為推薦的狀態(tài)管理解決方案,它和 Vue 生態(tài)系統(tǒng)中的其他核心庫一樣,都要經(jīng)過 RFC 流程,它的 API 也已經(jīng)進入穩(wěn)定狀態(tài)。
對比 Vuex 3.x/4.x?
Vuex 3.x 只適配 Vue 2,而 Vuex 4.x 是適配 Vue 3 的。
Pinia API 與 Vuex(<=4) 也有很多不同,即:
- mutation 已被棄用。它們經(jīng)常被認為是極其冗余的。它們初衷是帶來 devtools 的集成方案,但這已不再是一個問題了。
- 無需要創(chuàng)建自定義的復雜包裝器來支持 TypeScript,一切都可標注類型,API 的設計方式是盡可能地利用 TS 類型推理。
- 無過多的魔法字符串注入,只需要導入函數(shù)并調(diào)用它們,然后享受自動補全的樂趣就好。
- 無需要動態(tài)添加 Store,它們默認都是動態(tài)的,甚至你可能都不會注意到這點。注意,你仍然可以在任何時候手動使用一個 Store 來注冊它,但因為它是自動的,所以你不需要擔心它。
- 不再有嵌套結構的模塊。你仍然可以通過導入和使用另一個 Store 來隱含地嵌套 stores 空間。雖然 Pinia 從設計上提供的是一個扁平的結構,但仍然能夠在 Store 之間進行交叉組合。你甚至可以讓 Stores 有循環(huán)依賴關系。
- 不再有可命名的模塊??紤]到 Store 的扁平架構,Store 的命名取決于它們的定義方式,你甚至可以說所有 Store 都應該命名。
基礎示例?
先創(chuàng)建一個 Store:
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return { count: 0 }
},
// 也可以這樣定義
// state: () => ({ count: 0 })
actions: {
increment() {
this.count++
},
},
})
然后你就可以在一個組件中使用該 store 了:
<script setup>
import { useCounterStore } from '@/stores/counter'
const counter = useCounterStore()
counter.count++
// 自動補全! ?
counter.$patch({ count: counter.count + 1 })
// 或使用 action 代替
counter.increment()
</script>
<template>
<!-- 直接從 store 中訪問 state -->
<div>Current Count: {{ counter.count }}</div>
</template>
為實現(xiàn)更多高級用法,你甚至可以使用一個函數(shù) (與組件 setup()
類似) 來定義一個 Store:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
如果你還不熟悉 setup() 函數(shù)和組合式 API,別擔心,Pinia 也提供了一組類似 Vuex 的 映射 state 的輔助函數(shù)。你可以用和之前一樣的方式來定義 Store,然后通過 mapStores()
、mapState()
或 mapActions()
訪問:
const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
const useUserStore = defineStore('user', {
// ...
})
export default defineComponent({
computed: {
// 其他計算屬性
// ...
// 允許訪問 this.counterStore 和 this.userStore
...mapStores(useCounterStore, useUserStore)
// 允許讀取 this.count 和 this.double
...mapState(useCounterStore, ['count', 'double']),
},
methods: {
// 允許讀取 this.increment()
...mapActions(useCounterStore, ['increment']),
},
})
想學習更系統(tǒng)的學習Pinia, 請訪問《Pinia 中文教程》