跳至内容

状态

状态通常是 Store 的核心部分。人们通常从定义代表其应用程序的状态开始。在 Pinia 中,状态被定义为一个返回初始状态的函数。这使得 Pinia 可以在服务器端和客户端都正常工作。

js
import { defineStore } from 'pinia'

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

提示

如果您使用的是 Vue 2,您在 state 中创建的数据遵循与 Vue 实例中的 data 相同的规则,即状态对象必须是纯对象,并且您需要在**添加新**属性时调用 Vue.set()。**另请参阅:Vue#data**。

TypeScript

您不需要做太多事情来使您的状态与 TS 兼容:确保strict,或者至少noImplicitThis已启用,Pinia 将自动推断您的状态类型!但是,在某些情况下,您应该通过一些强制转换来帮助它。

ts
export const useUserStore = defineStore('user', {
  state: () => {
    return {
      // for initially empty lists
      userList: [] as UserInfo[],
      // for data that is not yet loaded
      user: null as UserInfo | null,
    }
  },
})

interface UserInfo {
  name: string
  age: number
}

如果您愿意,可以使用接口定义状态并为 state() 的返回值指定类型。

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

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

interface UserInfo {
  name: string
  age: number
}

访问 state

默认情况下,您可以通过 store 实例直接读取和写入状态。

js
const store = useStore()

store.count++

请注意,您不能添加新的状态属性**如果您没有在 state() 中定义它**。它必须包含初始状态。例如:如果 secondCount 未在 state() 中定义,我们不能执行 store.secondCount = 2

重置状态

选项 Store中,您可以通过在 Store 上调用 $reset() 方法将状态重置为其初始值。

js
const store = useStore()

store.$reset()

在内部,这将调用 state() 函数来创建一个新的状态对象,并用它替换当前状态。

设置 Store中,您需要创建自己的 $reset() 方法。

ts
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  function $reset() {
    count.value = 0
  }

  return { count, $reset }
})

与选项 API 一起使用

对于以下示例,您可以假设创建了以下 Store

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

import { defineStore } from 'pinia'

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

如果您没有使用 Composition API,并且您使用的是 computedmethods 等,您可以使用 mapState() 帮助程序将状态属性映射为只读计算属性。

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

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

可修改状态

如果您希望能够写入这些状态属性(例如,如果您有一个表单),您可以使用 mapWritableState()。请注意,您不能像使用 mapState() 一样传递函数。

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

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

提示

对于像数组这样的集合,您不需要 mapWritableState(),除非您要使用 cartItems = [] 替换整个数组,mapState() 仍然允许您对集合调用方法。

修改状态

除了使用 store.count++ 直接修改 Store 之外,您还可以调用 $patch 方法。它允许您使用部分 state 对象同时应用多个更改。

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

但是,某些修改使用这种语法非常困难或成本高昂:任何集合修改(例如,将元素推入、移除或拼接数组)都需要您创建一个新的集合。因此,$patch 方法还接受一个函数来对这些难以使用补丁对象应用的修改进行分组。

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

这里的主要区别在于 $patch() 允许您将多个更改分组到 devtools 中的一个条目。请注意,**对 state$patch() 的直接更改都会出现在 devtools 中**,并且可以进行时间旅行(Vue 3 中尚未实现)。

替换 state

您**不能完全替换** Store 的状态,因为这会破坏响应性。但是,您可以修补它

js
// this doesn't actually replace `$state`
store.$state = { count: 24 }
// it internally calls `$patch()`:
store.$patch({ count: 24 })

您还可以通过更改 pinia 实例的 state 来**设置整个应用程序的初始状态**。这在SSR 用于水合期间使用。

js
pinia.state.value = {}

订阅状态

您可以通过 Store 的 $subscribe() 方法观察状态及其更改,类似于 Vuex 的subscribe 方法。使用 $subscribe() 相对于常规 watch() 的优势在于,订阅修补后只会触发一次(例如,当使用上面的函数版本时)。

js
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))
})

默认情况下,状态订阅绑定到添加它们的组件(如果 Store 位于组件的 setup() 中)。这意味着,当组件卸载时,它们将自动移除。如果您还想在组件卸载后保留它们,请将 { detached: true } 作为第二个参数传递以分离当前组件的状态订阅

vue
<script setup>
const someStore = useSomeStore()

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

提示

您可以使用单个 watch()pinia 实例上观察整个状态。

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