Pinia State

2023-09-28 11:38 更新

State

在大多數(shù)情況下,state 都是你的 store 的核心。人們通常會先定義能代表他們 APP 的 state。在 Pinia 中,state 被定義為一個返回初始狀態(tài)的函數(shù)。這使得 Pinia 可以同時支持服務(wù)端和客戶端。

import { defineStore } from 'pinia'


const useStore = defineStore('storeId', {
  // 為了完整類型推理,推薦使用箭頭函數(shù)
  state: () => {
    return {
      // 所有這些屬性都將自動推斷出它們的類型
      count: 0,
      name: 'Eduardo',
      isAdmin: true,
      items: [],
      hasChanged: true,
    }
  },
})

:::tip 如果你使用的是 Vue 2,你在 state 中創(chuàng)建的數(shù)據(jù)與 Vue 實例中的 data 遵循同樣的規(guī)則,即 state 對象必須是清晰的,當(dāng)你想向其添加新屬性時,你需要調(diào)用 Vue.set() 。參考:Vue#data。 :::

TypeScript

你并不需要做太多努力就能使你的 state 兼容 TS。確保啟用了 strict,或者至少啟用了 noImplicitThis,Pinia 將自動推斷您的狀態(tài)類型! 但是,在某些情況下,您應(yīng)該幫助它進行一些轉(zhuǎn)換:

const useStore = defineStore('storeId', {
  state: () => {
    return {
      // 用于初始化空列表
      userList: [] as UserInfo[],
      // 用于尚未加載的數(shù)據(jù)
      user: null as UserInfo | null,
    }
  },
})


interface UserInfo {
  name: string
  age: number
}

如果你愿意,你可以用一個接口定義 state,并添加 state() 的返回值的類型。

interface State {
  userList: UserInfo[]
  user: UserInfo | null
}


const useStore = defineStore('storeId', {
  state: (): State => {
    return {
      userList: [],
      user: null,
    }
  },
})


interface UserInfo {
  name: string
  age: number
}

訪問 state

默認(rèn)情況下,你可以通過 store 實例訪問 state,直接對其進行讀寫。

const store = useStore()


store.count++

重置 state

使用選項式 API 時,你可以通過調(diào)用 store 的 $reset() 方法將 state 重置為初始值。

const store = useStore()


store.$reset()

使用選項式 API 的用法

<VueSchoolLink href="https://vueschool.io/lessons/access-pinia-state-in-the-options-api" title="Access Pinia State via the Options API" />

在下面的例子中,你可以假設(shè)相關(guān) store 已經(jīng)創(chuàng)建了:

// 示例文件路徑:
// ./src/stores/counter.js


import { defineStore } from 'pinia'


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

如果你不能使用組合式 API,但你可以使用 computed,methods,...,那你可以使用 mapState() 輔助函數(shù)將 state 屬性映射為只讀的計算屬性:

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


export default {
  computed: {
    // 可以訪問組件中的 this.count
    // 與從 store.count 中讀取的數(shù)據(jù)相同
    ...mapState(useCounterStore, ['count'])
    // 與上述相同,但將其注冊為 this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'count',
      // 你也可以寫一個函數(shù)來獲得對 store 的訪問權(quán)
      double: store => store.count * 2,
      // 它可以訪問 `this`,但它沒有標(biāo)注類型...
      magicValue(store) {
        return store.someGetter + this.count + this.double
      },
    }),
  },
}

可修改的 state

如果你想修改這些 state 屬性 (例如,如果你有一個表單),你可以使用 mapWritableState() 作為代替。但注意你不能像 mapState() 那樣傳遞一個函數(shù):

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


export default {
  computed: {
    // 可以訪問組件中的 this.count,并允許設(shè)置它。
    // this.count++
    // 與從 store.count 中讀取的數(shù)據(jù)相同
    ...mapWritableState(useCounterStore, ['count'])
    // 與上述相同,但將其注冊為 this.myOwnName
    ...mapWritableState(useCounterStore, {
      myOwnName: 'count',
    }),
  },
}

:::tip 對于像數(shù)組這樣的集合,你并不一定需要使用 mapWritableState(),mapState() 也允許你調(diào)用集合上的方法,除非你想用 cartItems = [] 替換整個數(shù)組。 :::

變更 state

<!-- TODO: disable this with strictMode -->

除了用 store.count++ 直接改變 store,你還可以調(diào)用 $patch 方法。它允許你用一個 state 的補丁對象在同一時間更改多個屬性:

store.$patch({
  count: store.count + 1,
  age: 120,
  name: 'DIO',
})

不過,用這種語法的話,有些變更真的很難實現(xiàn)或者很耗時:任何集合的修改(例如,向數(shù)組中添加、移除一個元素或是做 splice 操作)都需要你創(chuàng)建一個新的集合。因此,$patch 方法也接受一個函數(shù)來組合這種難以用補丁對象實現(xiàn)的變更。

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

<!-- TODO: disable this with strictMode, { noDirectPatch: true } -->

兩種變更 store 方法的主要區(qū)別是,$patch() 允許你將多個變更歸入 devtools 的同一個條目中。同時請注意,直接修改 state,$patch() 也會出現(xiàn)在 devtools 中,而且可以進行 time travel (在 Vue 3 中還沒有)。

替換 state

不能完全替換掉 store 的 state,因為那樣會破壞其響應(yīng)性。但是,你可以 patch 它。

// 這實際上并沒有替換`$state`
store.$state = { count: 24 }
// 在它內(nèi)部調(diào)用 `$patch()`:
store.$patch({ count: 24 })

你也可以通過變更 pinia 實例的 state 來設(shè)置整個應(yīng)用的初始 state。這常用于 SSR 中的激活過程。

pinia.state.value = {}

訂閱 state

類似于 Vuex 的 subscribe 方法,你可以通過 store 的 $subscribe() 方法偵聽 state 及其變化。比起普通的 watch(),使用 $subscribe() 的好處是 subscriptionspatch 后只觸發(fā)一次 (例如,當(dāng)使用上面的函數(shù)版本時)。

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  // 和 cartStore.$id 一樣
  mutation.storeId // 'cart'
  // 只有 mutation.type === 'patch object'的情況下才可用
  mutation.payload // 傳遞給 cartStore.$patch() 的補丁對象。


  // 每當(dāng)狀態(tài)發(fā)生變化時,將整個 state 持久化到本地存儲。
  localStorage.setItem('cart', JSON.stringify(state))
})

默認(rèn)情況下,state subscription 會被綁定到添加它們的組件上 (如果 store 在組件的 setup() 里面)。這意味著,當(dāng)該組件被卸載時,它們將被自動刪除。如果你想在組件卸載后依舊保留它們,請將 { detached: true } 作為第二個參數(shù),以將 state subscription 從當(dāng)前組件中分離

<script setup>
const someStore = useSomeStore()
// 此訂閱器即便在組件卸載之后仍會被保留
someStore.$subscribe(callback, { detached: true })
</script>

:::tip 你可以在 pinia 實例上使用 watch() 函數(shù)偵聽整個 state。

watch(
  pinia.state,
  (state) => {
    // 每當(dāng)狀態(tài)發(fā)生變化時,將整個 state 持久化到本地存儲。
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號