Pinia 是什么?

2023-09-28 15:22 更新

Pinia 起始于 2019 年 11 月左右的一次實(shí)驗(yàn),其目的是設(shè)計(jì)一個(gè)擁有組合式 API 的 Vue 狀態(tài)管理庫(kù)。從那時(shí)起,我們就傾向于同時(shí)支持 Vue 2 和 Vue 3,并且不強(qiáng)制要求開(kāi)發(fā)者使用組合式 API,我們的初心至今沒(méi)有改變。除了安裝SSR 兩章之外,其余章節(jié)中提到的 API 均支持 Vue 2 和 Vue 3。雖然本文檔主要是面向 Vue 3 的用戶,但在必要時(shí)會(huì)標(biāo)注出 Vue 2 的內(nèi)容,因此 Vue 2 和 Vue 3 的用戶都可以閱讀本文檔。

為什么你應(yīng)該使用 Pinia?

Pinia 是 Vue 的專屬狀態(tài)管理庫(kù),它允許你跨組件或頁(yè)面共享狀態(tài)。如果你熟悉組合式 API 的話,你可能會(huì)認(rèn)為可以通過(guò)一行簡(jiǎn)單的 export const state = reactive({}) 來(lái)共享一個(gè)全局狀態(tài)。對(duì)于單頁(yè)應(yīng)用來(lái)說(shuō)確實(shí)可以,但如果應(yīng)用在服務(wù)器端渲染,這可能會(huì)使你的應(yīng)用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型單頁(yè)應(yīng)用中,你也可以獲得如下功能:

  • Devtools 支持
    • 追蹤 actions、mutations 的時(shí)間線
    • 在組件中展示它們所用到的 Store
    • 讓調(diào)試更容易的 Time travel
  • 熱更新
    • 不必重載頁(yè)面即可修改 Store
    • 開(kāi)發(fā)時(shí)可保持當(dāng)前的 State
  • 插件:可通過(guò)插件擴(kuò)展 Pinia 功能
  • 為 JS 開(kāi)發(fā)者提供適當(dāng)?shù)?TypeScript 支持以及自動(dòng)補(bǔ)全功能。
  • 支持服務(wù)端渲染

基礎(chǔ)示例

下面就是 pinia API 的基本用法 (為繼續(xù)閱讀本簡(jiǎn)介請(qǐng)確保你已閱讀過(guò)了開(kāi)始章節(jié))。你可以先創(chuàng)建一個(gè) Store:

  1. // stores/counter.js
  2. import { defineStore } from 'pinia'
  3. export const useCounterStore = defineStore('counter', {
  4. state: () => {
  5. return { count: 0 }
  6. },
  7. // 也可以這樣定義
  8. // state: () => ({ count: 0 })
  9. actions: {
  10. increment() {
  11. this.count++
  12. },
  13. },
  14. })

然后你就可以在一個(gè)組件中使用該 store 了:

  1. <script setup>
  2. import { useCounterStore } from '@/stores/counter'
  3. const counter = useCounterStore()
  4. counter.count++
  5. // 自動(dòng)補(bǔ)全! ?
  6. counter.$patch({ count: counter.count + 1 })
  7. // 或使用 action 代替
  8. counter.increment()
  9. </script>
  10. <template>
  11. <!-- 直接從 store 中訪問(wèn) state -->
  12. <div>Current Count: {{ counter.count }}</div>
  13. </template>

為實(shí)現(xiàn)更多高級(jí)用法,你甚至可以使用一個(gè)函數(shù) (與組件 setup() 類似) 來(lái)定義一個(gè) Store:

  1. export const useCounterStore = defineStore('counter', () => {
  2. const count = ref(0)
  3. function increment() {
  4. count.value++
  5. }
  6. return { count, increment }
  7. })

如果你還不熟悉 setup() 函數(shù)和組合式 API,別擔(dān)心,Pinia 也提供了一組類似 Vuex 的 映射 state 的輔助函數(shù)。你可以用和之前一樣的方式來(lái)定義 Store,然后通過(guò) mapStores()mapState()mapActions() 訪問(wèn):

  1. const useCounterStore = defineStore('counter', {
  2. state: () => ({ count: 0 }),
  3. getters: {
  4. double: (state) => state.count * 2,
  5. },
  6. actions: {
  7. increment() {
  8. this.count++
  9. },
  10. },
  11. })
  12. const useUserStore = defineStore('user', {
  13. // ...
  14. })
  15. export default defineComponent({
  16. computed: {
  17. // 其他計(jì)算屬性
  18. // ...
  19. // 允許訪問(wèn) this.counterStore 和 this.userStore
  20. ...mapStores(useCounterStore, useUserStore)
  21. // 允許讀取 this.count 和 this.double
  22. ...mapState(useCounterStore, ['count', 'double']),
  23. },
  24. methods: {
  25. // 允許讀取 this.increment()
  26. ...mapActions(useCounterStore, ['increment']),
  27. },
  28. })

你將會(huì)在核心概念部分了解到更多關(guān)于每個(gè)映射輔助函數(shù)的信息。

為什么取名 Pinia

Pinia (發(fā)音為 /pi?nj?/,類似英文中的 “peenya”) 是最接近有效包名 pi?a (西班牙語(yǔ)中的 pineapple,即“菠蘿”) 的詞。 菠蘿花實(shí)際上是一組各自獨(dú)立的花朵,它們結(jié)合在一起,由此形成一個(gè)多重的水果。 與 Store 類似,每一個(gè)都是獨(dú)立誕生的,但最終它們都是相互聯(lián)系的。 它(菠蘿)也是一種原產(chǎn)于南美洲的美味熱帶水果。

更真實(shí)的示例

這是一個(gè)更完整的 Pinia API 示例,在 JavaScript 中也使用了類型提示。對(duì)于某些開(kāi)發(fā)者來(lái)說(shuō),可能足以在不進(jìn)一步閱讀的情況下直接開(kāi)始閱讀本節(jié)內(nèi)容,但我們?nèi)匀唤ㄗh你先繼續(xù)閱讀文檔的其余部分,甚至跳過(guò)此示例,在閱讀完所有核心概念之后再回來(lái)。

  1. import { defineStore } from 'pinia'
  2. export const useTodos = defineStore('todos', {
  3. state: () => ({
  4. /** @type {{ text: string, id: number, isFinished: boolean }[]} */
  5. todos: [],
  6. /** @type {'all' | 'finished' | 'unfinished'} */
  7. filter: 'all',
  8. // 類型將自動(dòng)推斷為 number
  9. nextId: 0,
  10. }),
  11. getters: {
  12. finishedTodos(state) {
  13. // 自動(dòng)補(bǔ)全! ?
  14. return state.todos.filter((todo) => todo.isFinished)
  15. },
  16. unfinishedTodos(state) {
  17. return state.todos.filter((todo) => !todo.isFinished)
  18. },
  19. /**
  20. * @returns {{ text: string, id: number, isFinished: boolean }[]}
  21. */
  22. filteredTodos(state) {
  23. if (this.filter === 'finished') {
  24. // 調(diào)用其他帶有自動(dòng)補(bǔ)全的 getters ?
  25. return this.finishedTodos
  26. } else if (this.filter === 'unfinished') {
  27. return this.unfinishedTodos
  28. }
  29. return this.todos
  30. },
  31. },
  32. actions: {
  33. // 接受任何數(shù)量的參數(shù),返回一個(gè) Promise 或不返回
  34. addTodo(text) {
  35. // 你可以直接變更該狀態(tài)
  36. this.todos.push({ text, id: this.nextId++, isFinished: false })
  37. },
  38. },
  39. })

對(duì)比 Vuex

Pinia 起源于一次探索 Vuex 下一個(gè)迭代的實(shí)驗(yàn),因此結(jié)合了 Vuex 5 核心團(tuán)隊(duì)討論中的許多想法。最后,我們意識(shí)到 Pinia 已經(jīng)實(shí)現(xiàn)了我們?cè)?Vuex 5 中想要的大部分功能,所以決定將其作為新的推薦方案來(lái)代替 Vuex。

與 Vuex 相比,Pinia 不僅提供了一個(gè)更簡(jiǎn)單的 API,也提供了符合組合式 API 風(fēng)格的 API,最重要的是,搭配 TypeScript 一起使用時(shí)有非??煽康念愋屯茢嘀С?。

RFC

最初,Pinia 沒(méi)有經(jīng)過(guò)任何 RFC 的流程。我基于自己開(kāi)發(fā)應(yīng)用的經(jīng)驗(yàn),同時(shí)通過(guò)閱讀其他人的代碼,為使用 Pinia 的用戶工作,以及在 Discord 上回答問(wèn)題等方式驗(yàn)證了一些想法。 這些經(jīng)歷使我產(chǎn)出了這樣一個(gè)可用的解決方案,并適應(yīng)了各種場(chǎng)景和應(yīng)用規(guī)模。我會(huì)一直在保持其核心 API 不變的情況下發(fā)布新版本,同時(shí)不斷優(yōu)化本庫(kù)。

現(xiàn)在 Pinia 已經(jīng)成為推薦的狀態(tài)管理解決方案,它和 Vue 生態(tài)系統(tǒng)中的其他核心庫(kù)一樣,都要經(jīng)過(guò) RFC 流程,它的 API 也已經(jīng)進(jìn)入穩(wěn)定狀態(tài)。

對(duì)比 Vuex 3.x/4.x

Vuex 3.x 只適配 Vue 2,而 Vuex 4.x 是適配 Vue 3 的。

Pinia API 與 Vuex(<=4) 也有很多不同,即:

  • mutation 已被棄用。它們經(jīng)常被認(rèn)為是極其冗余的。它們初衷是帶來(lái) devtools 的集成方案,但這已不再是一個(gè)問(wèn)題了。
  • 無(wú)需要?jiǎng)?chuàng)建自定義的復(fù)雜包裝器來(lái)支持 TypeScript,一切都可標(biāo)注類型,API 的設(shè)計(jì)方式是盡可能地利用 TS 類型推理。
  • 無(wú)過(guò)多的魔法字符串注入,只需要導(dǎo)入函數(shù)并調(diào)用它們,然后享受自動(dòng)補(bǔ)全的樂(lè)趣就好。
  • 無(wú)需要?jiǎng)討B(tài)添加 Store,它們默認(rèn)都是動(dòng)態(tài)的,甚至你可能都不會(huì)注意到這點(diǎn)。注意,你仍然可以在任何時(shí)候手動(dòng)使用一個(gè) Store 來(lái)注冊(cè)它,但因?yàn)樗亲詣?dòng)的,所以你不需要擔(dān)心它。
  • 不再有嵌套結(jié)構(gòu)的模塊。你仍然可以通過(guò)導(dǎo)入和使用另一個(gè) Store 來(lái)隱含地嵌套 stores 空間。雖然 Pinia 從設(shè)計(jì)上提供的是一個(gè)扁平的結(jié)構(gòu),但仍然能夠在 Store 之間進(jìn)行交叉組合。你甚至可以讓 Stores 有循環(huán)依賴關(guān)系。
  • 不再有可命名的模塊??紤]到 Store 的扁平架構(gòu),Store 的命名取決于它們的定義方式,你甚至可以說(shuō)所有 Store 都應(yīng)該命名。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)