菠蘿來啦@湃痢N糇帧爆袍!新一代VueX 來啦 他有一個特別甜的名字“Pinia”!再不學(xué)你就out了【 Pinia/Vuex5中文文檔】

官網(wǎng)直通車

What is Pinia作郭?

官網(wǎng)的介紹是:
Pinia最初是在 2019 年 11 月左右重新設(shè)計使用Composition API的 Vue Store 的實驗陨囊。從那時起,最初的原則仍然相同夹攒,但 Pinia 適用于 Vue 2 和 Vue 3 蜘醋,并且不需要你使用組合 API。除了安裝SSR之外咏尝,兩者的 API 都是相同的压语,并且這些文檔針對 Vue 3 ,并在必要時提供有關(guān) Vue 2 的注釋编检,以便 Vue 2 和 Vue 3 用戶可以閱讀胎食!

我的理解是:
Pinia 是 Vue.js 的輕量級狀態(tài)管理庫,最近很受歡迎允懂。它使用 Vue 3 中的新反應(yīng)系統(tǒng)來構(gòu)建一個直觀且完全類型化的狀態(tài)管理庫厕怜。

Pinia的成功可以歸功于其管理存儲數(shù)據(jù)的獨特功能(可擴展性、存儲模塊組織蕾总、狀態(tài)變化分組粥航、多存儲創(chuàng)建等)。

另一方面生百,Vuex也是為Vue框架建立的一個流行的狀態(tài)管理庫递雀,它也是Vue核心團隊推薦的狀態(tài)管理庫。 Vuex高度關(guān)注應(yīng)用程序的可擴展性蚀浆、開發(fā)人員的工效和信心缀程。它基于與Redux相同的流量架構(gòu)搜吧。

自3月份掘金夜談No.1猶大重比提到了Pinia,我關(guān)注到了這個庫杠输。
尤大明確表示了:“不會有vuex5赎败,或者說pinia就是vuex5。pinia是vuex的維護者在決定vuex5風(fēng)格的時候做的一個庫蠢甲,效果比預(yù)期的好,同時為了尊重作者据忘,pinia也不會更名為vuex5”
Pinia 是 Vue 的存儲庫鹦牛,它允許您跨組件/頁面共享狀態(tài)。如果您熟悉 Composition API勇吊,您可能會認(rèn)為您已經(jīng)可以使用簡單的export const state = reactive({}). 這對于單頁應(yīng)用程序來說是正確的曼追,但如果它是服務(wù)器端呈現(xiàn)的,則會將您的應(yīng)用程序暴露給安全漏洞汉规。 但即使在小型單頁應(yīng)用程序中礼殊,您也可以從使用 Pinia 中獲得很多好處:

  • 開發(fā)工具支持

    • A timeline to track actions, mutations
    • Stores appear in components where they are used
    • Time travel and easier debugging
  • 熱模塊更換

    • 在不重新加載頁面的情況下修改您的Store
    • 在開發(fā)時保持任何現(xiàn)有狀態(tài)
  • 插件:使用插件擴展 Pinia 功能

  • 為 JS 用戶提供適當(dāng)?shù)?TypeScript 支持或自動完成功能

  • 服務(wù)器端渲染支持

基本示例

這就是使用 pinia 在 API 方面的樣子(請務(wù)必查看官網(wǎng)中入門以獲取完整說明)。您首先創(chuàng)建一個Store:

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => {
    return { count: 0 }
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++
    },
  },
})

然后在組件中使用它:

import { useCounterStore } from '@/stores/counter'

export default {
  setup() {
    const counter = useCounterStore()

    counter.count++
    // with autocompletion ?
    counter.$patch({ count: counter.count + 1 })
    // or using an action instead
    counter.increment()
  },
}

你甚至可以使用一個函數(shù)(類似于一個組件setup())來為更高級的用例定義一個 Store:

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  function increment() {
    count.value++
  }

  return { count, increment }
})

如果您仍然不熟悉setup()Composition API针史,請不要擔(dān)心晶伦,Pinia 還支持一組類似的map helpers,例如 Vuex啄枕。您以相同的方式定義存儲婚陪,但隨后使用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 {
  computed: {
    // other computed properties
    // ...
    // gives access to this.counterStore and this.userStore
    ...mapStores(useCounterStore, useUserStore)
    // gives read access to this.count and this.double
    ...mapState(useCounterStore, ['count', 'double']),
  },
  methods: {
    // gives access to this.increment()
    ...mapActions(useCounterStore, ['increment']),
  },
}

您將在核心概念中找到有關(guān)每個地圖助手的更多信息频祝。

為什么選擇Pinia#

Pinia(發(fā)音為/pi?nj?/泌参,如英語中的“peenya”)是最接近pi?a(西班牙語中的菠蘿)的詞,它是一個有效的包名稱常空。菠蘿實際上是一組單獨的花朵沽一,它們結(jié)合在一起形成多個水果。與Store類似漓糙,每一家都是獨立誕生的铣缠,但最終都是相互聯(lián)系的。它也是一種美味的熱帶水果兼蜈,原產(chǎn)于南美洲攘残。

一個更現(xiàn)實的例子#

這是一個更完整的 API 示例,您將在 Pinia中使用類型为狸,即使在 JavaScript 中也是如此歼郭。對于某些人來說,這可能足以在不進一步閱讀的情況下開始使用辐棒,但我們?nèi)匀唤ㄗh檢查文檔的其余部分病曾,甚至跳過此示例牍蜂,并在閱讀完所有核心概念后返回。

import { defineStore } from 'pinia'

export const todos = defineStore('todos', {
  state: () => ({
    /** @type {{ text: string, id: number, isFinished: boolean }[]} */
    todos: [],
    /** @type {'all' | 'finished' | 'unfinished'} */
    filter: 'all',
    // type will be automatically inferred to number
    nextId: 0,
  }),
  getters: {
    finishedTodos(state) {
      // autocompletion! ?
      return state.todos.filter((todo) => todo.isFinished)
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) => !todo.isFinished)
    },
    /**
     * @returns {{ text: string, id: number, isFinished: boolean }[]}
     */
    filteredTodos(state) {
      if (this.filter === 'finished') {
        // call other getters with autocompletion ?
        return this.finishedTodos
      } else if (this.filter === 'unfinished') {
        return this.unfinishedTodos
      }
      return this.todos
    },
  },
  actions: {
    // any amount of arguments, return a promise or not
    addTodo(text) {
      // you can directly mutate the state
      this.todos.push({ text, id: this.nextId++, isFinished: false })
    },
  },
})

與 Vuex 的比較#

Pinia 最初是為了探索 Vuex 的下一次迭代會是什么樣子泰涂,結(jié)合了 Vuex 5 核心團隊討論中的許多想法鲫竞。最終,我們意識到 Pinia 已經(jīng)實現(xiàn)了我們在 Vuex 5 中想要的大部分內(nèi)容逼蒙,并決定實現(xiàn)它取而代之的是新的建議从绘。

與 Vuex 相比,Pinia 提供了一個更簡單的 API是牢,具有更少的儀式僵井,提供了 Composition-API 風(fēng)格的 API,最重要的是驳棱,在與 TypeScript 一起使用時具有可靠的類型推斷支持批什。

RFC#

最初 Pinia 沒有通過任何 RFC。我根據(jù)我開發(fā)應(yīng)用程序社搅、閱讀其他人的代碼驻债、為使用 Pinia 的客戶工作以及在 Discord 上回答問題的經(jīng)驗測試了想法。這使我能夠提供一種適用于各種情況和應(yīng)用程序大小的解決方案形葬。我過去經(jīng)常發(fā)布并在保持其核心 API 不變的同時使庫不斷發(fā)展合呐。

現(xiàn)在 Pinia 已經(jīng)成為默認(rèn)的狀態(tài)管理解決方案,它和 Vue 生態(tài)系統(tǒng)中的其他核心庫一樣遵循 RFC 流程荷并,其 API 也進入了穩(wěn)定狀態(tài)合砂。

與 Vuex 3.x/4.x 的比較#

Vuex 3.x 對應(yīng)的是 Vue 2 而 Vuex 4.x 是 Vue 3

Pinia API 與 Vuex ≤4 有很大不同,即:

  • 突變不再存在源织。他們經(jīng)常被認(rèn)為是非常冗長的翩伪。他們最初帶來了 devtools 集成,但這不再是問題谈息。
  • 無需創(chuàng)建自定義復(fù)雜包裝器來支持 TypeScript缘屹,所有內(nèi)容都是類型化的,并且 API 的設(shè)計方式盡可能利用 TS 類型推斷侠仇。
  • 不再需要注入魔法字符串轻姿、導(dǎo)入函數(shù)、調(diào)用它們逻炊,享受自動完成功能互亮!
  • 無需動態(tài)添加Store,默認(rèn)情況下它們都是動態(tài)的余素,您甚至都不會注意到豹休。請注意,您仍然可以隨時手動使用Store進行注冊桨吊,但因為它是自動的威根,您無需擔(dān)心凤巨。
  • 不再有模塊的嵌套結(jié)構(gòu)。您仍然可以通過在另一個Store中導(dǎo)入和使用Store來隱式嵌套Store洛搀,但 Pinia 通過設(shè)計提供平面結(jié)構(gòu)敢茁,同時仍然支持Store之間的交叉組合方式。你甚至可以有 store 的循環(huán)依賴留美。
  • 沒有命名空間的模塊彰檬。鑒于Store的扁平架構(gòu),“命名空間”Store是其定義方式所固有的谎砾,您可以說所有Store都是命名空間的僧叉。

有關(guān)如何將現(xiàn)有 Vuex ≤4 項目轉(zhuǎn)換為使用 Pinia 的更詳細(xì)說明,請參閱官網(wǎng)從 Vuex 遷移指南棺榔。

安裝#

pinia使用您最喜歡的包管理器安裝:

yarn add pinia
# or with npm
npm install pinia
# or with pnpm
pnpm install pinia

提示

如果您的應(yīng)用使用 Vue 2,您還需要安裝組合 api: @vue/composition-api隘道。如果您使用 Nuxt症歇,則應(yīng)遵循這些說明

如果你使用的是 Vue CLI谭梗,你可以試試這個非官方的插件忘晤。

創(chuàng)建一個 pinia(根存儲)并將其傳遞給應(yīng)用程序:

import { createPinia } from 'pinia'

app.use(createPinia())

如果您使用的是 Vue 2,您還需要安裝一個插件并pinia在應(yīng)用程序的根目錄注入創(chuàng)建的插件:

import { createPinia, PiniaVuePlugin } from 'pinia'

Vue.use(PiniaVuePlugin)
const pinia = createPinia()

new Vue({
  el: '#app',
  // other options...
  // ...
  // note the same `pinia` instance can be used across multiple Vue apps on
  // the same page
  pinia,
})

這也將添加 devtools 支持激捏。在 Vue 3 中设塔,仍然不支持時間旅行和編輯等一些功能,因為 vue-devtools 尚未公開必要的 API远舅,但 devtools 具有更多功能闰蛔,并且整體開發(fā)人員體驗要優(yōu)越得多。在 Vue 2 中图柏,Pinia 使用 Vuex 的現(xiàn)有接口(因此不能與它一起使用)序六。

什么是 Store?#

一個Store(如 Pinia)是一個實體蚤吹,它持有未綁定到您的組件樹的狀態(tài)和業(yè)務(wù)邏輯例诀。換句話說,它托管全局狀態(tài)裁着。它有點像一個始終存在并且每個人都可以讀取和寫入的組件繁涂。它包含三個概念狀態(tài)二驰、getterAction扔罪,并且可以安全地假設(shè)這些概念等同于data,computedmethods在組件中。

我什么時候應(yīng)該使用Store#

存儲應(yīng)該包含可以在整個應(yīng)用程序中訪問的數(shù)據(jù)诸蚕。這包括在許多地方使用的數(shù)據(jù)步势,例如在導(dǎo)航欄中顯示的用戶信息氧猬,以及需要通過頁面保存的數(shù)據(jù),例如非常復(fù)雜的多步驟表單坏瘩。

另一方面盅抚,您應(yīng)該避免在存儲中包含可能托管在組件中的本地數(shù)據(jù),例如頁面本地元素的可見性倔矾。

并非所有應(yīng)用程序都需要訪問全局狀態(tài)妄均,但如果您需要一個,Pania 將使您的生活更輕松哪自。

定義Store

在深入研究核心概念之前丰包,我們需要知道存儲是使用定義的defineStore(),并且它需要一個唯一的名稱壤巷,作為第一個參數(shù)傳遞:

import { defineStore } from 'pinia'

// useStore could be anything like useUser, useCart
// the first argument is a unique id of the store across your application
export const useStore = defineStore('main', {
  // other options...
})

這個名稱邑彪,也稱為id,是必要的胧华,Pania 使用它來將Store連接到 devtools寄症。將返回的函數(shù)命名為use... 是可組合項之間的約定,以使其使用習(xí)慣矩动。

使用Store#

我們正在定義useStore()一個Store有巧,因為Store在被調(diào)用之前不會被創(chuàng)建setup()

import { useStore } from '@/stores/counter'

export default {
  setup() {
    const store = useStore()

    return {
      // you can return the whole store instance to use it in the template
      store,
    }
  },
}

您可以根據(jù)需要定義任意數(shù)量的Store,并且應(yīng)該在不同的文件中定義每個Store以充分利用 pinia(例如自動允許您的包進行代碼拆分和 TypeScript 推理)悲没。

如果您還沒有使用setup組件篮迎,您仍然可以將 Pinia 與map helpers一起使用。

實例化Store后示姿,您可以直接在Store中訪問定義在state甜橱、getters和中的任何屬性。actions我們將在接下來的頁面中詳細(xì)介紹這些內(nèi)容峻凫,但自動補全會對您有所幫助渗鬼。

請注意,這store是一個用 包裹的對象reactive荧琼,這意味著不需要.value在 getter 之后編寫譬胎,但是像propsin一樣setup我們不能對其進行解構(gòu)

export default defineComponent({
  setup() {
    const store = useStore()
    // ? This won't work because it breaks reactivity
    // it's the same as destructuring from `props`
    const { name, doubleCount } = store

    name // "eduardo"
    doubleCount // 2

    return {
      // will always be "eduardo"
      name,
      // will always be 2
      doubleCount,
      // this one will be reactive
      doubleValue: computed(() => store.doubleCount),
      }
  },
})

為了從存儲中提取屬性同時保持其反應(yīng)性命锄,您需要使用storeToRefs(). 它將為每個反應(yīng)屬性創(chuàng)建參考堰乔。當(dāng)您僅使用Store中的狀態(tài)但不調(diào)用任何操作時,這很有用脐恩。請注意镐侯,您可以直接從Store中解構(gòu)操作,因為它們也綁定到Store本身:

import { storeToRefs } from 'pinia'

export default defineComponent({
  setup() {
    const store = useStore()
    // `name` and `doubleCount` are reactive refs
    // This will also create refs for properties added by plugins
    // but skip any action or non reactive (non ref/reactive) property
    const { name, doubleCount } = storeToRefs(store)
    // the increment action can be just extracted
    const { increment } = store

    return {
      name,
      doubleCount
      increment,
    }
  },
})

狀態(tài) State

大多數(shù)時候,州是Store的中心部分苟翻。人們通常從定義代表他們的應(yīng)用程序的狀態(tài)開始韵卤。在 Pinia 中,狀態(tài)被定義為返回初始狀態(tài)的函數(shù)崇猫。這允許 Pinia 在服務(wù)器端和客戶端工作沈条。

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // arrow function recommended for full type inference
  state: () => {
    return {
      // all these properties will have their type inferred automatically
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})

提示

如果您使用 Vue 2,您在其中創(chuàng)建的數(shù)據(jù)state遵循與在 Vue 實例中相同的規(guī)則data诅炉,即狀態(tài)對象必須是普通的蜡歹,并且您需要在向其添加新屬性Vue.set()時調(diào)用。另請參閱: Vue#data涕烧。********

訪問state#

默認(rèn)情況下月而,您可以通過store實例訪問狀態(tài)來直接讀取和寫入狀態(tài):

const store = useStore()

store.counter++

重置狀態(tài)#

您可以通過調(diào)用store 上的方法將狀態(tài)重置為其初始值:$reset()

const store = useStore()

store.$reset()

使用選項 API

對于以下示例,您可以假設(shè)已創(chuàng)建以下Store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  })
})

setup()#

雖然 Composition API 并不適合所有人议纯,但setup()鉤子可以使在 Options API 中使用 Pinia 變得更容易父款。不需要額外的map helper功能!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    tripleCounter() {
      return this.counterStore.counter * 3
    },
  },
}

沒有setup()#

如果您沒有使用 Composition API瞻凤,而您正在使用computed, methods, ...铛漓,則可以使用mapState()幫助器將State屬性映射為只讀計算屬性:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.counter inside the component
    // same as reading from store.counter
    ...mapState(useCounterStore, ['counter'])
    // same as above but registers it as this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'counter',
      // you can also write a function that gets access to the store
      double: store => store.counter * 2,
      // it can have access to `this` but it won't be typed correctly...
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

可修改State#

如果您希望能夠?qū)懭脒@些#### State屬性(例如,如果您有一個表單)鲫构,您可以mapWritableState()改用。請注意玫坛,您不能傳遞類似 with 的函數(shù)mapState()

import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.counter inside the component and allows setting it
    // this.counter++
    // same as reading from store.counter
    ...mapWritableState(useCounterStore, ['counter'])
    // same as above but registers it as this.myOwnName
    ...mapWritableState(useCounterStore, {
      myOwnName: 'counter',
    }),
  },
}

提示

您不需要mapWritableState()像數(shù)組這樣的集合结笨,除非您將整個數(shù)組替換為cartItems = [],mapState()仍然允許您調(diào)用集合上的方法。

改變狀態(tài)#

除了直接用 改變 store 之外store.counter++湿镀,您還可以調(diào)用該$patch方法炕吸。state它允許您對部分對象同時應(yīng)用多個更改:

store.$patch({
  counter: store.counter + 1,
  name: 'Abalam',
})

但是,使用這種語法應(yīng)用某些突變確實很難或成本很高:任何集合修改(例如勉痴,從數(shù)組中推送赫模、刪除、拼接元素)都需要您創(chuàng)建一個新集合蒸矛。正因為如此瀑罗,該$patch方法還接受一個函數(shù)來對這種難以用補丁對象應(yīng)用的突變進行分組:

cartStore.$patch((state) => {
  state.items.push({ name: 'shoes', quantity: 1 })
  state.hasChanged = true
})

這里的主要區(qū)別是$patch()允許您將多個更改分組到 devtools 中的一個條目中。請注意 雏掠,直接更改state$patch()出現(xiàn)在 devtools 中斩祭,并且可以穿越時間(在 Vue 3 中還沒有)。

更換state#

$state您可以通過將Store的屬性設(shè)置為新對象來替換Store的整個狀態(tài):

store.$state = { counter: 666, name: 'Paimon' }

您還可以通過更改實例state的來替換應(yīng)用程序的整個狀態(tài)乡话。pinia這在SSR 期間用于補水摧玫。

pinia.state.value = {}

訂閱狀態(tài)#

你可以通過 store 的方法觀察狀態(tài)及其變化$subscribe(),類似于 Vuex 的subscribe 方法绑青。$subscribe()與常規(guī)相比使用的優(yōu)點watch()訂閱只會在補丁后觸發(fā)一次(例如诬像,使用上面的函數(shù)版本時)屋群。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // same as cartStore.$id
  mutation.storeId // 'cart'
  // only available with mutation.type === 'patch object'
  mutation.payload // patch object passed to cartStore.$patch()

  // persist the whole state to the local storage whenever it changes
  localStorage.setItem('cart', JSON.stringify(state))
})

默認(rèn)情況下,狀態(tài)訂閱綁定到添加它們的組件(如果存儲在組件的內(nèi)部setup())坏挠。意思是芍躏,當(dāng)組件被卸載時,它們將被自動刪除癞揉。如果要在組件卸載后保留它們纸肉,請{ detached: true }作為第二個參數(shù)傳遞以從當(dāng)前組件中分離 狀態(tài)訂閱:

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept after the component is unmounted
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

提示

pinia您可以查看實例上的整個狀態(tài):

watch(
  pinia.state,
  (state) => {
    // persist the whole state to the local storage whenever it changes
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)

Getters

Getter 完全等同于 Store 狀態(tài)的計算值。它們可以用 中的getters屬性定義defineStore()喊熟。他們接收state作為第一個參數(shù)來鼓勵使用箭頭函數(shù):

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    doubleCount: (state) => state.counter * 2,
  },
})

大多數(shù)時候柏肪,getter 只會依賴狀態(tài),但是芥牌,他們可能需要使用其他 getter烦味。因此,我們可以在定義常規(guī)函數(shù)時訪問整個store 實例壁拉, 但需要定義返回類型的類型(在 TypeScript 中) 谬俄。這是由于 TypeScript 中的一個已知限制,不會影響使用箭頭函數(shù)定義的 getter弃理,也不會影響不使用的 getterthis**** this

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // automatically infers the return type as a number
    doubleCount(state) {
      return state.counter * 2
    },
    // the return type **must** be explicitly set
    doublePlusOne(): number {
      // autocompletion and typings for the whole store ?
      return this.doubleCount + 1
    },
  },
})

然后你可以直接在 store 實例上訪問 getter:

<template>
  <p>Double count is {{ store.doubleCount }}</p>
</template>

<script>
export default {
  setup() {
    const store = useStore()

    return { store }
  },
}
</script>

訪問其他 getter#

與計算屬性一樣溃论,您可以組合多個 getter。通過 訪問任何其他Gettersthis痘昌。即使您不使用 TypeScript钥勋,您也可以使用JSDoc提示您的 IDE 類型:

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  getters: {
    // type is automatically inferred because we are not using `this`
    doubleCount: (state) => state.counter * 2,
    // here we need to add the type ourselves (using JSDoc in JS). We can also
    // use this to document the getter
    /**
     * Returns the counter value times two plus one.
     *
     * @returns {number}
     */
    doubleCountPlusOne() {
      // autocompletion ?
      return this.doubleCount + 1
    },
  },
})

將參數(shù)傳遞給 getter#

Getter只是在幕后計算的屬性,因此不可能將任何參數(shù)傳遞給它們辆苔。但是算灸,您可以從getter返回一個函數(shù)以接受任何參數(shù):

export const useStore = defineStore('main', {
  getters: {
    getUserById: (state) => {
      return (userId) => state.users.find((user) => user.id === userId)
    },
  },
})

并在組件中使用:

<script>
export default {
  setup() {
    const store = useStore()

    return { getUserById: store.getUserById }
  },
}
</script>

<template>
  <p>User 2: {{ getUserById(2) }}</p>
</template>

請注意,執(zhí)行此操作時驻啤,getter 不再緩存菲驴,它們只是您調(diào)用的函數(shù)。但是骑冗,您可以在 getter 本身內(nèi)部緩存一些結(jié)果赊瞬,這并不常見,但應(yīng)該證明性能更高:

export const useStore = defineStore('main', {
  getters: {
    getActiveUserById(state) {
      const activeUsers = state.users.filter((user) => user.active)
      return (userId) => activeUsers.find((user) => user.id === userId)
    },
  },
})

訪問其他Store的Getters#

要使用其他存儲 getter贼涩,您可以直接在getter內(nèi)部使用它:**

import { useOtherStore } from './other-store'

export const useStore = defineStore('main', {
  state: () => ({
    // ...
  }),
  getters: {
    otherGetter(state) {
      const otherStore = useOtherStore()
      return state.localData + otherStore.data
    },
  },
})

用法與setup()#

您可以直接訪問任何 getter 作為 store 的屬性(與 state 屬性完全一樣):

export default {
  setup() {
    const store = useStore()

    store.counter = 3
    store.doubleCount // 6
  },
}

使用選項 API

對于以下示例森逮,您可以假設(shè)已創(chuàng)建以下Store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  getters: {
    doubleCounter() {
      return this.counter * 2
    }
  }
})

setup()#

雖然 Composition API 并不適合所有人,但setup()鉤子可以使在 Options API 中使用 Pinia 變得更容易磁携。不需要額外的map helper功能褒侧!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  computed: {
    quadrupleCounter() {
      return counterStore.doubleCounter * 2
    },
  },
}

沒有setup()#

您可以使用上一部分狀態(tài)中使用的相同mapState()函數(shù)來映射到 getter:

import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  computed: {
    // gives access to this.doubleCounter inside the component
    // same as reading from store.doubleCounter
    ...mapState(useCounterStore, ['doubleCount'])
    // same as above but registers it as this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'doubleCounter',
      // you can also write a function that gets access to the store
      double: store => store.doubleCount,
    }),
  },
}

Actions

Actions相當(dāng)于組件中的方法。它們可以使用actionsin 屬性進行定義,defineStore()并且非常適合定義業(yè)務(wù)邏輯

export const useStore = defineStore('main', {
  state: () => ({
    counter: 0,
  }),
  actions: {
    increment() {
      this.counter++
    },
    randomizeCounter() {
      this.counter = Math.round(100 * Math.random())
    },
  },
})

getter一樣闷供,action通過完全輸入(和自動完成?)支持訪問整個Store實例烟央。與它們不同的是,它 可以是異步的歪脏,您可以在它們內(nèi)部進行任何 API 調(diào)用甚至其他操作疑俭!這是一個使用Mande的示例。請注意婿失,您使用的庫并不重要钞艇,只要您獲得 a ,您甚至可以使用本機函數(shù)(僅限瀏覽器):this**** actionsawaitPromise``fetch

import { mande } from 'mande'

const api = mande('/api/users')

export const useUsers = defineStore('users', {
  state: () => ({
    userData: null,
    // ...
  }),

  actions: {
    async registerUser(login, password) {
      try {
        this.userData = await api.post({ login, password })
        showTooltip(`Welcome back ${this.userData.name}!`)
      } catch (error) {
        showTooltip(error)
        // let the form component display the error
        return error
      }
    },
  },
})

你也可以完全自由地設(shè)置你想要的任何參數(shù)并返回任何東西豪硅。調(diào)用action時哩照,一切都會自動推斷!

action像方法一樣被調(diào)用:

export default defineComponent({
  setup() {
    const main = useMainStore()
    // call the action as a method of the store
    main.randomizeCounter()

    return {}
  },
})

訪問其他Store操作#

要使用另一個Store懒浮,您可以直接在action內(nèi)部使用它:**

import { useAuthStore } from './auth-store'

export const useSettingsStore = defineStore('settings', {
  state: () => ({
    preferences: null,
    // ...
  }),
  actions: {
    async fetchUserPreferences() {
      const auth = useAuthStore()
      if (auth.isAuthenticated) {
        this.preferences = await fetchPreferences()
      } else {
        throw new Error('User must be authenticated')
      }
    },
  },
})

用法與setup()#

您可以直接調(diào)用任何操作作為 store 的方法:

export default {
  setup() {
    const store = useStore()

    store.randomizeCounter()
  },
}

使用選項 API

對于以下示例飘弧,您可以假設(shè)已創(chuàng)建以下Store:

// Example File Path:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

const useCounterStore = defineStore('counterStore', {
  state: () => ({
    counter: 0
  }),
  actions: {
    increment() {
      this.counter++
    }
  }
})

setup()#

雖然 Composition API 并不適合所有人,但setup()鉤子可以使在 Options API 中使用 Pinia 變得更容易砚著。不需要額外的map helper功能次伶!

import { useCounterStore } from '../stores/counterStore'

export default {
  setup() {
    const counterStore = useCounterStore()

    return { counterStore }
  },
  methods: {
    incrementAndPrint() {
      this.counterStore.increment()
      console.log('New Count:', this.counterStore.count)
    },
  },
}

沒有setup()#

如果您根本不想使用 Composition API,可以使用mapActions()幫助器將操作屬性映射為組件中的方法:

import { mapActions } from 'pinia'
import { useCounterStore } from '../stores/counterStore'

export default {
  methods: {
    // gives access to this.increment() inside the component
    // same as calling from store.increment()
    ...mapActions(useCounterStore, ['increment'])
    // same as above but registers it as this.myOwnName()
    ...mapActions(useCounterStore, { myOwnName: 'doubleCounter' }),
  },
}

訂閱操作#

可以用 觀察Action及其結(jié)果store.$onAction()稽穆。傳遞給它的回調(diào)在操作本身之前執(zhí)行冠王。after處理承諾并允許您在操作解決后執(zhí)行功能。以類似的方式舌镶,onError允許您在操作拋出或拒絕時執(zhí)行函數(shù)版确。這些對于在運行時跟蹤錯誤很有用,類似于Vue 文檔中的這個技巧乎折。

這是一個在運行操作之前和它們解決/拒絕之后記錄的示例。

const unsubscribe = someStore.$onAction(
  ({
    name, // name of the action
    store, // store instance, same as `someStore`
    args, // array of parameters passed to the action
    after, // hook after the action returns or resolves
    onError, // hook if the action throws or rejects
  }) => {
    // a shared variable for this specific action call
    const startTime = Date.now()
    // this will trigger before an action on `store` is executed
    console.log(`Start "${name}" with params [${args.join(', ')}].`)

    // this will trigger if the action succeeds and after it has fully run.
    // it waits for any returned promised
    after((result) => {
      console.log(
        `Finished "${name}" after ${
          Date.now() - startTime
        }ms.\nResult: ${result}.`
      )
    })

    // this will trigger if the action throws or returns a promise that rejects
    onError((error) => {
      console.warn(
        `Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
      )
    })
  }
)

// manually remove the listener
unsubscribe()

默認(rèn)情況下侵歇,操作訂閱綁定到添加它們的組件(如果Store位于組件的 內(nèi)部setup())骂澄。意思是,當(dāng)組件被卸載時惕虑,它們將被自動刪除坟冲。如果要在卸載組件后保留它們,請true作為第二個參數(shù)傳遞以將操作訂閱與當(dāng)前組件分離:**

export default {
  setup() {
    const someStore = useSomeStore()

    // this subscription will be kept after the component is unmounted
    someStore.$onAction(callback, true)

    // ...
  },
}

Plugins 插件

由于低級 API溃蔫,Pania Store可以完全擴展健提。以下是您可以執(zhí)行的操作列表:

  • 向Store添加新屬性
  • 定義Store時添加新選項
  • 向Store添加新方法
  • 包裝現(xiàn)有方法
  • 更改甚至取消操作
  • 實現(xiàn)像本地存儲這樣的副作用
  • 僅適用于特定Store

插件被添加到 pinia 實例中pinia.use()。最簡單的例子是通過返回一個對象為所有Store添加一個靜態(tài)屬性:

import { createPinia } from 'pinia'

// add a property named `secret` to every store that is created after this plugin is installed
// this could be in a different file
function SecretPiniaPlugin() {
  return { secret: 'the cake is a lie' }
}

const pinia = createPinia()
// give the plugin to pinia
pinia.use(SecretPiniaPlugin)

// in another file
const store = useStore()
store.secret // 'the cake is a lie'

這對于添加全局對象(如路由器伟叛、模式或 toast 管理器)很有用私痹。

介紹#

Pinia 插件是一個函數(shù),可以選擇返回要添加到Store的屬性。它需要一個可選參數(shù)紊遵,一個context

export function myPiniaPlugin(context) {
  context.pinia // the pinia created with `createPinia()`
  context.app // the current app created with `createApp()` (Vue 3 only)
  context.store // the store the plugin is augmenting
  context.options // the options object defining the store passed to `defineStore()`
  // ...
}

然后將此函數(shù)傳遞給piniawith pinia.use()

pinia.use(myPiniaPlugin)

插件僅適用于傳遞給應(yīng)用 后創(chuàng)建pinia的Store账千,否則將不會被應(yīng)用威恼。

擴充Store#

您可以通過簡單地在插件中返回它們的對象來為每個Store添加屬性:

pinia.use(() => ({ hello: 'world' }))

您也可以直接在 上設(shè)置屬性坤检,store如果可能,請使用返回版本门驾,以便它們可以被 devtools 自動跟蹤

pinia.use(({ store }) => {
  store.hello = 'world'
})

插件返回的任何屬性都將由 devtools 自動跟蹤学搜,因此為了hello在 devtools 中可見娃善,請確保僅當(dāng)您想在 devtools 中調(diào)試它時才將其添加到store._customProperties dev 模式:

// from the example above
pinia.use(({ store }) => {
  store.hello = 'world'
  // make sure your bundler handle this. webpack and vite should do it by default
  if (process.env.NODE_ENV === 'development') {
    // add any keys you set on the store
    store._customProperties.add('hello')
  }
})

請注意,每個Store都用 包裝reactive瑞佩,自動解包裝它包含的任何 Ref ( ref(), computed(), ...):

const sharedRef = ref('shared')
pinia.use(({ store }) => {
  // each store has its individual `hello` property
  store.hello = ref('secret')
  // it gets automatically unwrapped
  store.hello // 'secret'

  // all stores are sharing the value `shared` property
  store.shared = sharedRef
  store.shared // 'shared'
})

這就是為什么您可以在沒有的情況下訪問所有計算屬性.value以及它們是反應(yīng)性的原因聚磺。

添加新狀態(tài)#

如果您想將新的狀態(tài)屬性添加到Store或打算在水合期間使用的屬性,您必須在兩個地方添加它

  • store所以你可以訪問它store.myState
  • store.$state因此它可以在 devtools 中使用钉凌,并在SSR 期間被序列化咧最。

請注意,這允許您共享reforcomputed屬性:

const globalSecret = ref('secret')
pinia.use(({ store }) => {
  // `secret` is shared among all stores
  store.$state.secret = globalSecret
  store.secret = globalSecret
  // it gets automatically unwrapped
  store.secret // 'secret'

  const hasError = ref(false)
  store.$state.hasError = hasError
  // this one must always be set
  store.hasError = toRef(store.$state, 'hasError')

  // in this case it's better not to return `hasError` since it
  // will be displayed in the `state` section in the devtools
  // anyway and if we return it, devtools will display it twice.
})

請注意御雕,插件中發(fā)生的狀態(tài)更改或添加(包括調(diào)用store.$patch())發(fā)生在Store處于活動狀態(tài)之前矢沿,因此不會觸發(fā)任何訂閱

警告

如果您使用的是Vue 2酸纲,Pinia 會受到與Vue相同的反應(yīng)性警告捣鲸。創(chuàng)建新的狀態(tài)屬性時需要使用setfrom ,例如and :@vue/composition-api``secret``hasError

import { set } from '@vue/composition-api'
pinia.use(({ store }) => {
  if (!store.$state.hasOwnProperty('hello')) {
    const secretRef = ref('secret')
    // If the data is meant to be used during SSR, you should
    // set it on the `$state` property so it is serialized and
    // picked up during hydration
    set(store.$state, 'secret', secretRef)
    // set it directly on the store too so you can access it
    // both ways: `store.$state.secret` / `store.secret`
    set(store, 'secret', secretRef)
    store.secret // 'secret'
  }
})

添加新的外部屬性#

當(dāng)添加外部屬性闽坡、來自其他庫的類實例或僅僅是非反應(yīng)性的東西時栽惶,您應(yīng)該在將對象markRaw()傳遞給 pinia 之前將其包裝起來。這是一個將路由器添加到每個Store的示例:

import { markRaw } from 'vue'
// adapt this based on where your router is
import { router } from './router'

pinia.use(({ store }) => {
  store.router = markRaw(router)
})

$subscribe在插件內(nèi)部調(diào)用#

您也可以在插件中使用store.subscribe](https://pinia.vuejs.org/core-concepts/state.html#subscribing-to-the-state)和[store.onAction :

pinia.use(({ store }) => {
  store.$subscribe(() => {
    // react to store changes
  })
  store.$onAction(() => {
    // react to store actions
  })
})

添加新選項#

可以在定義Store時創(chuàng)建新選項疾嗅,以便以后從插件中使用它們外厂。例如,您可以創(chuàng)建一個debounce允許您對任何操作進行去抖動的選項:

defineStore('search', {
  actions: {
    searchContacts() {
      // ...
    },
  },

  // this will be read by a plugin later on
  debounce: {
    // debounce the action searchContacts by 300ms
    searchContacts: 300,
  },
})

然后插件可以讀取該選項以包裝操作并替換原始操作:

// use any debounce library
import debounce from 'lodash/debunce'

pinia.use(({ options, store }) => {
  if (options.debounce) {
    // we are overriding the actions with new ones
    return Object.keys(options.debounce).reduce((debouncedActions, action) => {
      debouncedActions[action] = debounce(
        store[action],
        options.debounce[action]
      )
      return debouncedActions
    }, {})
  }
})

請注意代承,使用設(shè)置語法時汁蝶,自定義選項作為第三個參數(shù)傳遞:

defineStore(
  'search',
  () => {
    // ...
  },
  {
    // this will be read by a plugin later on
    debounce: {
      // debounce the action searchContacts by 300ms
      searchContacts: 300,
    },
  }
)

TypeScript#

上面顯示的所有內(nèi)容都可以通過鍵入支持來完成,因此您無需使用any@ts-ignore.

Typing plugins 插件#

Pinia 插件可以按如下方式鍵入:

import { PiniaPluginContext } from 'pinia'

export function myPiniaPlugin(context: PiniaPluginContext) {
  // ...
}

鍵入新的Store屬性#

向Store添加新屬性時论悴,您還應(yīng)該擴展PiniaCustomProperties接口掖棉。

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties {
    // by using a setter we can allow both strings and refs
    set hello(value: string | Ref<string>)
    get hello(): string

    // you can define simpler values too
    simpleNumber: number
  }
}

然后可以安全地寫入和讀取它:

pinia.use(({ store }) => {
  store.hello = 'Hola'
  store.hello = ref('Hola')

  store.simpleNumber = Math.random()
  // @ts-expect-error: we haven't typed this correctly
  store.simpleNumber = ref(Math.random())
})

PiniaCustomProperties是一種通用類型,允許您引用Store的屬性膀估。想象以下示例幔亥,我們將初始選項復(fù)制為$options(這僅適用于選項存儲):

pinia.use(({ options }) => ({ $options: options }))

我們可以通過使用 4 種通用類型來正確輸入PiniaCustomProperties

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomProperties<Id, S, G, A> {
    $options: {
      id: Id
      state?: () => S
      getters?: G
      actions?: A
    }
  }
}

提示

在泛型中擴展類型時,它們的命名必須與源代碼中的完全相同察纯。Id不能命名idI帕棉,S也不能命名State针肥。以下是每個字母所代表的含義:

  • S: State
  • G: Getters
  • A: Actions
  • SS: Setup Store / Store

輸入新狀態(tài)#

當(dāng)添加新的狀態(tài)屬性(storestore.$state)時,您需要添加類型來PiniaCustomStateProperties代替笤昨。與 不同的是PiniaCustomProperties祖驱,它只接收State泛型:

import 'pinia'

declare module 'pinia' {
  export interface PiniaCustomStateProperties<S> {
    hello: string
  }
}

鍵入新的創(chuàng)建選項#

在為 創(chuàng)建新選項時defineStore(),您應(yīng)該擴展DefineStoreOptionsBase. 與 不同的是PiniaCustomProperties瞒窒,它只公開了兩個泛型:State 和 Store 類型捺僻,允許您限制可以定義的內(nèi)容。例如崇裁,您可以使用操作的名稱:

import 'pinia'

declare module 'pinia' {
  export interface DefineStoreOptionsBase<S, Store> {
    // allow defining a number of ms for any of the actions
    debounce?: Partial<Record<keyof StoreActions<Store>, number>>
  }
}

提示

還有一種類型可以從 Store 類型StoreGetters中提取getter 匕坯。 您也可以通過分別擴展類型和來擴展設(shè)置Store選項Store 的選項。****DefineStoreOptions``DefineSetupStoreOptions

Nuxt.js#

Nuxt 旁邊使用 pinia 時拔稳,您必須先創(chuàng)建一個Nuxt 插件葛峻。這將使您可以訪問該pinia實例:

// plugins/myPiniaPlugin.js
import { PiniaPluginContext } from 'pinia'
import { Plugin } from '@nuxt/types'

function MyPiniaPlugin({ store }: PiniaPluginContext) {
  store.$subscribe((mutation) => {
    // react to store changes
    console.log(`[?? ${mutation.storeId}]: ${mutation.type}.`)
  })

  // Note this has to be typed if you are using TS
  return { creationTime: new Date() }
}

const myPlugin: Plugin = ({ $pinia }) => {
  $pinia.use(MyPiniaPlugin)
}

export default myPlugin

請注意,上面的示例使用的是 TypeScript巴比,如果您使用的是文件术奖,則必須刪除類型注釋PiniaPluginContext及其導(dǎo)入。Plugin``.js

在組件之外使用存儲

Pinia Store依靠pinia實例在所有調(diào)用中共享同一個Store實例轻绞。useStore()大多數(shù)情況下采记,只需調(diào)用您的函數(shù)即可開箱即用。例如政勃,在 中setup()唧龄,您無需執(zhí)行任何其他操作。但在組件之外奸远,情況有些不同既棺。在幕后,useStore() pinia您提供給app. 這意味著如果pinia無法自動注入實例懒叛,則必須手動將其提供給useStore()函數(shù)丸冕。您可以根據(jù)您正在編寫的應(yīng)用程序的類型以不同的方式解決這個問題。

單頁應(yīng)用程序#

如果你沒有做任何 SSR(服務(wù)器端渲染)薛窥,useStore()安裝 pinia 插件后的任何調(diào)用都app.use(pinia)將起作用:

import { useUserStore } from '@/stores/user'
import { createApp } from 'vue'
import App from './App.vue'

// ?  fails because it's called before the pinia is created
const userStore = useUserStore()

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

// ? works because the pinia instance is now active
const userStore = useUserStore()

確保始終應(yīng)用此功能的最簡單方法是通過將調(diào)用放在將始終在 pinia 安裝后運行的函數(shù)中來推遲調(diào)用胖烛。useStore()

讓我們看一下這個使用 Vue Router 的導(dǎo)航守衛(wèi)內(nèi)部的Store的例子:

import { createRouter } from 'vue-router'
const router = createRouter({
  // ...
})

// ? Depending on the order of imports this will fail
const store = useStore()

router.beforeEach((to, from, next) => {
  // we wanted to use the store here
  if (store.isLoggedIn) next()
  else next('/login')
})

router.beforeEach((to) => {
  // ? This will work because the router starts its navigation after
  // the router is installed and pinia will be installed too
  const store = useStore()

  if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})

SSR 應(yīng)用#

在處理服務(wù)器端渲染時,您必須將pinia實例傳遞給useStore(). 這可以防止 pinia 在不同的應(yīng)用程序?qū)嵗g共享全局狀態(tài)拆檬。

在SSR 指南中有一個專門的部分,這只是一個簡短的解釋:

服務(wù)器端渲染 (SSR)

提示

如果您使用的是Nuxt.js妥凳, 則需要閱讀這些說明竟贯。

只要您在useStore()函數(shù)頂部調(diào)用函數(shù),使用 Pinia 創(chuàng)建Store就可以立即使用 SSR setup逝钥,getters并且actions

export default defineComponent({
  setup() {
    // this works because pinia knows what application is running inside of
    // `setup()`
    const main = useMainStore()
    return { main }
  },
})

在外面使用Storesetup()#

如果您需要在其他地方使用Store屑那,則需要將傳遞給應(yīng)用程序pinia的實例傳遞給函數(shù)調(diào)用:useStore()

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

app.use(router)
app.use(pinia)

router.beforeEach((to) => {
  // ? This will work make sure the correct store is used for the
  // current running app
  const main = useMainStore(pinia)

  if (to.meta.requiresAuth && !main.isLoggedIn) return '/login'
})

Pinia 方便地將自身添加$pinia到您的應(yīng)用程序中拱镐,因此您可以在以下功能中使用它serverPrefetch()

export default {
  serverPrefetch() {
    const store = useStore(this.$pinia)
  },
}

State hydration#

為了水合初始狀態(tài),您需要確保 rootState 包含在 HTML 中的某個位置持际,以便 Pinia 稍后獲取它沃琅。根據(jù)您用于 SSR的內(nèi)容,出于安全原因蜘欲,您應(yīng)該轉(zhuǎn)義該狀態(tài)益眉。我們建議使用Nuxt.js 使用的@nuxt/ devalue:

import devalue from '@nuxt/devalue'
import { createPinia } from 'pinia'
// retrieve the rootState server side
const pinia = createPinia()
const app = createApp(App)
app.use(router)
app.use(pinia)

// after rendering the page, the root state is build and can be read directly
// on `pinia.state.value`.

// serialize, escape (VERY important if the content of the state can be changed
// by the user, which is almost always the case), and place it somewhere on
// the page, for example, as a global variable.
devalue(pinia.state.value)

根據(jù)您用于 SSR 的內(nèi)容,您將設(shè)置將在 HTML 中序列化的初始狀態(tài)變量姥份。您還應(yīng)該保護自己免受 XSS 攻擊郭脂。例如,使用vite-ssr您可以使用transformState選項@nuxt/devalue

import devalue from '@nuxt/devalue'

export default viteSSR(
  App,
  {
    routes,
    transformState(state) {
      return import.meta.env.SSR ? devalue(state) : state
    },
  },
  ({ initialState }) => {
    // ...
    if (import.meta.env.SSR) {
      // this will be stringified and set to window.__INITIAL_STATE__
      initialState.pinia = pinia.state.value
    } else {
      // on the client side, we restore the state
      pinia.state.value = initialState.pinia
    }
  }
)

您可以根據(jù)需要使用其他替代方案@nuxt/devalue澈歉,例如展鸡,如果您可以使用JSON.stringify()/序列化和解析您的狀態(tài)JSON.parse(),則可以大大提高您的性能埃难。

使此策略適應(yīng)您的環(huán)境莹弊。useStore()在調(diào)用客戶端的任何函數(shù)之前,請確保水合 pinia 的狀態(tài)涡尘。例如忍弛,如果我們將狀態(tài)序列化為一個<script>標(biāo)簽,使其可以在客戶端通過 全局訪問window.__pinia悟衩,我們可以這樣寫:

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

// must be set by the user
if (isClient) {
  pinia.state.value = JSON.parse(window.__pinia)
}

Nuxt.js#

將 Pinia 與Nuxt.js一起使用更容易剧罩,因為 Nuxt 在服務(wù)器端渲染方面處理了很多事情。例如座泳,您不需要關(guān)心序列化或 XSS 攻擊惠昔。

安裝#

確保安裝在旁邊:@nuxtjs/composition-apipinia

yarn add pinia @pinia/nuxt @nuxtjs/composition-api
# or with npm
npm install pinia @pinia/nuxt @nuxtjs/composition-api
# or with pnpm
pnpm install pinia @pinia/nuxt @nuxtjs/composition-api

我們提供了一個模塊來為您處理所有事情,您只需將其添加到buildModules您的nuxt.config.js文件中:

// nuxt.config.js
export default {
  // ... other options
  buildModules: [
    // Nuxt 2 only:
    // https://composition-api.nuxtjs.org/getting-started/setup#quick-start
    '@nuxtjs/composition-api/module',
    '@pinia/nuxt',
  ],
}

就是這樣挑势,像往常一樣使用您的Store镇防!

在外面使用Storesetup()#

如果你想在外面使用一個 store setup(),記得把pinia對象傳給useStore(). 我們將它添加到上下文中asyncData()潮饱,因此您可以在and中訪問它fetch()

import { useStore } from '~/stores/myStore'

export default {
  asyncData({ $pinia }) {
    const store = useStore($pinia)
  },
}

在Store中使用 Nuxt 上下文#

您還可以通過使用注入屬性在任何Store中使用上下文$nuxt

import { useUserStore } from '~/stores/userStore'

defineStore('cart', {
  actions: {
    purchase() {
      const user = useUserStore()
      if (!user.isAuthenticated()) {
        this.$nuxt.redirect('/login')
      }
    },
  },
})

將 Pinia 與 Vuex 一起使用#

建議避免同時使用 Pinia 和 Vuex来氧,但如果您需要同時使用兩者,則需要告訴 pinia 不要禁用它:

// nuxt.config.js
export default {
  buildModules: [
    '@nuxtjs/composition-api/module',
    ['@pinia/nuxt', { disableVuex: false }],
  ],
  // ... other options
}

TypeScript#

如果您使用的是 TypeScript 或有jsconfig.json香拉,您還應(yīng)該添加以下類型context.pinia

{
  "types": [
    // ...
    "@pinia/nuxt"
  ]
}

這也將確保您具有自動完成功能??啦扬。

不使用setup()#

即使您不使用組合 API,也可以使用 Pinia(如果您使用的是 Vue 2凫碌,您仍然需要安裝@vue/composition-api插件)扑毡。雖然我們建議您嘗試使用 Composition API 并學(xué)習(xí)它,但您和您的團隊可能還不是時候盛险,您可能正在遷移應(yīng)用程序或任何其他原因瞄摊。有幾個功能:

授予對整個Store的訪問權(quán)限#

如果您需要訪問Store中的幾乎所有內(nèi)容勋又,則映射Store的每個屬性可能太多了......相反,您可以通過以下方式訪問整個StoremapStores()

import { mapStores } from 'pinia'

// given two stores with the following ids
const useUserStore = defineStore('user', {
  // ...
})
const useCartStore = defineStore('cart', {
  // ...
})

export default {
  computed: {
    // note we are not passing an array, just one store after the other
    // each store will be accessible as its id + 'Store'
    ...mapStores(useCartStore, useUserStore)
  },

  methods: {
    async buyStuff() {
      // use them anywhere!
      if (this.userStore.isAuthenticated()) {
        await this.cartStore.buy()
        this.$router.push('/purchased')
      }
    },
  },
}

默認(rèn)情況下换帜,Pania 會為每個Store添加"Store"后綴楔壤。id您可以通過調(diào)用來自定義此行為setMapStoreSuffix()

import { createPinia, setMapStoreSuffix } from 'pinia'

// completely remove the suffix: this.user, this.cart
setMapStoreSuffix('')
// this.user_store, this.cart_store (it's okay, I won't judge you)
setMapStoreSuffix('_store')
export const pinia = createPinia()

TypeScript#

默認(rèn)情況下,所有地圖助手都支持自動完成惯驼,你不需要做任何事情蹲嚣。如果您調(diào)用setMapStoreSuffix()更改"Store"后綴,您還需要將其添加到 TS 文件或您的文件中的某個位置global.d.ts跳座。最方便的地方是您打電話的地方setMapStoreSuffix()

import { createPinia, setMapStoreSuffix } from 'pinia'

setMapStoreSuffix('') // completely remove the suffix
export const pinia = createPinia()

declare module 'pinia' {
  export interface MapStoresCustomization {
    // set it to the same value as above
    suffix: ''
  }
}

警告

如果您使用的是 TypeScript 聲明文件(如global.d.ts)端铛,請確保import 'pinia'在其頂部公開所有現(xiàn)有類型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疲眷,一起剝皮案震驚了整個濱河市禾蚕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狂丝,老刑警劉巖换淆,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異几颜,居然都是意外死亡倍试,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門蛋哭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來县习,“玉大人,你說我怎么就攤上這事谆趾≡暝福” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵沪蓬,是天一觀的道長彤钟。 經(jīng)常有香客問我,道長跷叉,這世上最難降的妖魔是什么逸雹? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮云挟,結(jié)果婚禮上梆砸,老公的妹妹穿的比我還像新娘。我一直安慰自己园欣,他們只是感情好帖世,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俊庇,像睡著了一般狮暑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辉饱,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天搬男,我揣著相機與錄音,去河邊找鬼彭沼。 笑死缔逛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姓惑。 我是一名探鬼主播褐奴,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼于毙!你這毒婦竟也來了敦冬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤唯沮,失蹤者是張志新(化名)和其女友劉穎脖旱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體介蛉,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡萌庆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了币旧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片践险。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吹菱,靈堂內(nèi)的尸體忽然破棺而出巍虫,到底是詐尸還是另有隱情,我是刑警寧澤毁葱,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布垫言,位于F島的核電站,受9級特大地震影響倾剿,放射性物質(zhì)發(fā)生泄漏筷频。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一前痘、第九天 我趴在偏房一處隱蔽的房頂上張望凛捏。 院中可真熱鬧,春花似錦芹缔、人聲如沸坯癣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽示罗。三九已至惩猫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚜点,已是汗流浹背轧房。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绍绘,地道東北人奶镶。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像陪拘,于是被迫代替她去往敵國和親厂镇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容