State

在大多数情况下,state 都是你的 store 的核心。人们通常会首先定义能代表他们应用程序的 state。在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。

import { defineStore } from 'pinia'

const useStore = defineStore('storeId', {
  // 为了完整类型推理,推荐使用箭头函数
  state: () => {
    return {
      // 所有这些属性都将自动推断出它们的类型
      counter: 0,
      name: 'Eduardo',
      isAdmin: true,
    }
  },
})

TIP

如果你使用的是 Vue2,你在 state 中创建的数据与 Vue 实例中的 data 遵循同样的规则,即 state 对象必须是清晰的,你需要在向其添加新属性时调用 Vue.set()参考:Vue#data

访问 state

默认情况下,你可以通过 store 实例访问 state,直接对其进行读写。

const store = useStore()

store.counter++

重置 state

你可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

const store = useStore()

store.$reset()

使用选项式 API 的用法

对于下面的例子,你可以假设相关 Store 已经创建了:

// 示例文件路径:
// ./src/stores/counterStore.js

import { defineStore } from 'pinia',

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

使用 setup()

虽然组合式 API 并不适合所有人,但 setup() 钩子可以使 Pinia 在选项式 API 中更容易操作。并且不需要额外的 map helper 函数!

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

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

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

不使用 setup()

如果你不使用组合式 API,你可以使用 computedmethods,...,你也可以使用mapState() helper 将 state 属性映射为只读的计算属性:

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

export default {
  computed: {
    // 可以访问组件中的 this.counter。
    // 与从 store.counter 中读取的数据相同
    ...mapState(useCounterStore, ['counter'])
    // 与上述相同,但将其注册为 this.myOwnName
    ...mapState(useCounterStore, {
      myOwnName: 'counter',
      // 你也可以写一个函数来获得对 store 的访问权
      double: store => store.counter * 2,
      // 它可以访问 "this",但它无法被正确类型检查。。。
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

可修改的 state

如果你让这些 state 属性可写(例如,如果你有一个表单),你可以使用 mapWritableState() 来代替。但注意你不能像 mapState() 那样传递一个函数:

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

export default {
  computed: {
    // 可以访问组件中的this.counter,并允许设置它。
    // this.counter++
    // 与从 store.counter 中读取的数据相同
    ...mapWritableState(useCounterStore, ['counter'])
    // 与上述相同,但将其注册为 this.myOwnName
    ...mapWritableState(useCounterStore, {
      myOwnName: 'counter',
    }),
  },
}

TIP

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

变更 state

除了用 store.counter++ 直接改变存储,你还可以调用 $patch 方法。它允许你用一个局部的 state 对象在同一时间应用多个变化:

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

然而,用这种语法的话,有些变更真的很难实现或很耗时:任何集合的修改(例如,从数组中推送、移除、拼接一个元素)都需要你创建一个新的集合。因此,$patch 方法也接受一个函数来分组这种难以用补丁对象实现的变更。

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

这里的主要区别是,$patch() 允许你将多个改动归入 devtools 中的一个条目。注意两点,直接修改 state然后 $patch() 就会出现在 devtools 中,并且可以进行 time travelled(在 Vue3 中还没有)。

替换 state

你可以通过将一个 store 的 $state 属性设置为一个新的对象来替换它的整个 state:

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

你也可以通过改变 pinia 实例的 state 来替换应用程序的整个 state。这在常用于 SSR for hydration

pinia.state.value = {}

订阅 state

类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法观测 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptionspatches 后只触发一次(例如,当使用上面的函数版本时)。

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() 的补丁对象。

  // 每当状态发生变化时,将整个 state 持久化到本地存储。
  localStorage.setItem('cart', JSON.stringify(state))
})

默认情况下,state subscriptions 会被绑定到它们被添加的组件上(如果 store 在组件的 setup() 里面)。这意味着,当该组件被卸载时,它们将被自动删除。如果你想在组件卸载后保留它们,请将 { detached: true } 作为第二个参数,以将 state subscription 从当前组件中剥离:

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

    // 在组件被卸载后,该订阅将被保留。
    someStore.$subscribe(callback, { detached: true })

    // ...
  },
}

TIP

你可以在pinia实例上侦听整个 state。

watch(
  pinia.state,
  (state) => {
    // 每当状态发生变化时,将整个 state 持久化到本地存储。
    localStorage.setItem('piniaState', JSON.stringify(state))
  },
  { deep: true }
)