跳至主要內容

State

Zhao Bin笔记frontendpiniapinia

State

The state is, most of the time, the central part of your store.
In Pinia the state is defined as a function that returns the initial state.

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

提示

If you are using Vue 2, the data you create in state follows the same rules as the data in a Vue instance,
ie the state object must be plain and you need to call Vue.set() when adding new properties to it.
See also: Vue#dataopen in new window.

Accessing the state

By default, you can directly read and write to the state by accessing it through the store instance:

const store = useStore()
store.counter++

Resetting the state

You can reset the state to its initial value by calling the $reset() method on the store:

const store = useStore()
store.$reset()

Usage with the Options API

// src/stores/counterStore.js
import { defineStore } from 'pinia'

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

If you are not using the Composition API, and you are using computed, methods, ...,
you can use the mapState() helper to map state properties as readonly computed properties:

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 is 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 correctlly
      magicValue(store) {
        return store.someGetter + this.counter + this.double
      },
    }),
  },
}

Modifiable state

If you want to be able to write to these state properties (e.g. if you have a form),
you can use mapWritableState() instead.
Note you cannot pass a function like with 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',
    }),
  },
}

提示

You don't need mapWritableState() for collections like arrays unless you are replacing the whole array with cartItems = [],
mapState() still allows you to call methods on your collections.

Usage with the Options API

Mutating the state

Apart from directly mutating the store with store.counter++, you can also call the $patch method.
It allows you to apply multiple changes at the same time with a partial state object:

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

However, some mutations are really hard or costly to apply with this syntax: any collection modification
(e.g. pushing, removing, splicing an element from an array) requires you to create a new collection.
Because of this, the $patch method also accepts a function to group this kind of mutations that are difficult to apply with a patch object:

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

Replacing the state

You can replace the whole state of a store by setting its $state property to a new object:

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

You can also replace the whole state of your application by changing the state of the pinia instance:

pinia.state.value = {}

Subscribing to the state

You can watch the state and its changes through the $subscribe() method of a store, similar to Vuex's subscribe method.

The advantage of using $subscribe() over a regular watch() is that subscriptions will trigger only once after patches (e.g. when using the function version from above).

cartStore.$subscribe((mutation, state) => {
  // import { MutationType } from 'pinia'
  mutation.type // 'direct' | 'patch object' | 'patch function'
  mutation.storeId // cart
  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))
})

By default, state subscriptions are bound to the component where they are added (if the store is inside a component's setup()).
Meaning, they will be automatically removed when the component is unmounted.

If you want to keep them after the component is unmounted,
pass { detached: true } as the second argument to detach the state subscription from the current component:

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

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

You can watch the whole state on the pinia instance:

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