Состояние
Состояние (state) - это, чаще всего, центральная часть вашего хранилища. Люди часто начинают с определения состояния, которое представляет их приложение. В Pinia состояние определяется как функция, которая возвращает начальное состояние. Это позволяет Pinia работать как на серверной, так и на клиентской стороне.
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
// рекомендуется стрелочная функция для полного вывода типа
state: () => {
return {
// для всех этих свойств тип будет определяться автоматически
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
Совет
Если вы используете Vue 2, данные, которые вы создаете в state
, следуют тем же правилам, что и data
в экземпляре Vue, то есть объект состояния должен быть простым, и вам нужно вызывать Vue.set()
, когда вы добавляете новые свойства в него. См. также: Vue#data
TypeScript
Для того чтобы сделать ваше состояние совместимым с TypeScript, вам не нужно много делать: убедитесь, что включен флаг strict
или, по крайней мере, noImplicitThis
, и Pinia автоматически будет выводить тип вашего состояния! Тем не менее, есть несколько случаев, когда вам следует помочь TypeScript с некоторыми явными преобразованиями типов:
export const useUserStore = defineStore('user', {
state: () => {
return {
// для изначально пустых списков
userList: [] as UserInfo[],
// для данных, которые еще не загружены
user: null as UserInfo | null,
}
},
})
interface UserInfo {
name: string
age: number
}
Если вы хотите, вы можете определить состояние с использованием интерфейса и указать тип возвращаемого state()
значения:
interface State {
userList: UserInfo[]
user: UserInfo | null
}
export const useUserStore = defineStore('user', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
interface UserInfo {
name: string
age: number
}
Доступ к состоянию
По умолчанию вы можете напрямую читать и записывать состояние, обращаясь к нему через экземпляр store
:
const store = useStore()
store.count++
Обратите внимание, что нельзя добавить новое свойство состояния если оно не было определено в state()
. Оно должно содержаться в начальном состоянии. Например: мы не можем сделать store.secondCount = 2
, если secondCount
не определено в state()
.
Сброс состояния
В option-хранилищах вы можете сбросить состояние до его начального значения, вызвав на хранилище метод $reset()
:
const store = useStore()
store.$reset()
При этом вызывается функция state()
, которая создает новый объект state и заменяет им текущее состояние.
В setup-хранилищах необходимо создать собственный метод $reset()
:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
Использование с Options API
Предположим, для следующих примеров было создано хранилище:
// Пример пути файла:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
})
Если вы не используете Composition API, а используете computed
, methods
, ..., вы можете использовать помощник mapState()
для отображения свойств состояния как вычисляемых свойств, доступных только для чтения:
import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
computed: {
// предоставляет доступ к this.count внутри компонента
// то же самое, что и чтение из store.count
...mapState(useCounterStore, ['count'])
// то же самое, что и выше, но регистрирует его как this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'count',
// можно также написать функцию, которая получает доступ к хранилищу
double: store => store.count * 2,
// может иметь доступ к `this`, но это не будет корректно типизировано...
magicValue(store) {
return store.someGetter + this.count + this.double
},
}),
},
}
Modifiable state
Если вы хотите иметь возможность записи у этих свойства состояния (например, если у вас есть форма), вы можете использовать mapWritableState()
вместо mapState()
. Обратите внимание, что вы не можете передать функцию, как это делается с mapState()
:
import { mapWritableState } from 'pinia'
import { useCounterStore } from '../stores/counter'
export default {
computed: {
// предоставляет доступ к this.count внутри компонента и позволяет изменять его
// this.count++
// то же самое, что и чтение из store.count
...mapWritableState(useCounterStore, ['count']),
// то же самое, что и выше, но регистрирует его под именем this.myOwnName
...mapWritableState(useCounterStore, {
myOwnName: 'count',
}),
},
}
Совет
Вам не нужно использовать mapWritableState()
для коллекций, таких как массивы, если вы не заменяете всю коллекцию, например, cartItems = []
. mapState()
все равно позволяет вызывать методы на ваших коллекциях.
Изменение состояния
Помимо прямого изменения хранилища с помощью store.count++
, вы также можете вызвать метод $patch
. Он позволяет вам применить несколько изменений одновременно с частью объекта state
:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
Тем не менее, некоторые изменения действительно сложно или дорого применять с этим синтаксисом: любое изменение коллекции (например, добавление, удаление, присоединение элемента к массиву) требует создания новой коллекции. По этой причине метод $patch
также принимает функцию для группировки таких изменений, которые сложно применять с помощью объекта:
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
Основное отличие заключается в том, что $patch()
позволяет сгруппировать несколько изменений в одну запись в devtools. Обратите внимание, что и прямые изменения в state
, и $patch()
появляются в devtools и могут быть перемещены во времени (в Vue 3 этого пока нет).
Замена состояния
Вы не можете напрямую заменить состояние хранилища, так как это нарушило бы реактивность. Однако вы можете изменять его:
// на самом деле это не заменяет `$state`
store.$state = { count: 24 }
// а внутренне вызывает `$patch()`:
store.$patch({ count: 24 })
Вы также можете установить начальное состояние всего вашего приложения, изменив state
экземпляра pinia
. Это используется во время рендеринга на стороне сервера (SSR) для гитратации.
pinia.state.value = {}
Подписка на состояние
Вы можете следить за состоянием и его изменениями с помощью метода хранилища $subscribe()
, аналогичного методу subscribe во Vuex. Преимущество использования $subscribe()
перед обычным watch()
заключается в том, что подписки будут срабатывать только один раз после изменений (например, при использовании функциональной версии, как в примере выше).
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 // patch object passed to cartStore.$patch()
// сохранять все состояние в local storage при каждом его изменении
localStorage.setItem('cart', JSON.stringify(state))
})
По умолчанию подписки на состояние привязаны к компоненту, в котором они добавлены (если хранилище находится в setup()
компонента). Это означает, что они будут автоматически удалены, когда компонент будет размонтирован. Если вы также хотите сохранить их после того, как компонент будет размонтирован, передайте { detached: true }
в качестве второго аргумента, чтобы отсоединить подписку на состояние от текущего компонента:
<script setup>
const someStore = useSomeStore()
// эта подписка будет сохраняться даже после размонтирования компонента
someStore.$subscribe(callback, { detached: true })
</script>
Совет
Вы можете наблюдать за всем состоянием экземпляра pinia
с помощью одного watch()
:
watch(
pinia.state,
(state) => {
// сохранять все состояние в local storage при каждом его изменении
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)