Стан
Стан - це, здебільшого, центральна частина вашого сховища. Люди часто починають з визначення стану, який представляє їхній додаток. У Pinia стан визначається як функція, яка повертає початковий стан. Це дозволяє Pinia працювати як на стороні сервера, так і на стороні клієнта.
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
// рекомендовано використовувати стрілочну функцію для автоматичного визначення типів
state: () => {
return {
// для всіх цих властивостей тип буде визначено автоматично
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
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 автоматично визначить тип вашого стану! Проте, є кілька випадків, коли вам варто допомогти їй з цим:
export const useUserStore = defineStore('user', {
state: () => {
return {
// для початково порожніх списків
userList: [] as UserInfo[],
// для даних, які ще не завантажено
user: null as UserInfo | null,
}
},
})
interface UserInfo {
name: string
age: number
}
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
}
interface State {
userList: UserInfo[]
user: UserInfo | null
}
export const useUserStore = defineStore('user', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
interface UserInfo {
name: string
age: number
}
Доступ до стану
За замовчуванням, ви можете безпосередньо читати та записувати значення властивостей в стані, отримавши доступ до нього через екземпляр сховища
:
const store = useStore()
store.count++
const store = useStore()
store.count++
Зверніть увагу, що ви не можете додати нову властивість стану, якщо ви не оголосили її в state()
, вона повинна містити початковий стан. Наприклад: ви не можете зробити store.secondCount = 2
, якщо secondCount
не оголошено в state()
.
Скидання стану
В опційних сховищах ви можете обнулити стан до початкового значення, викликавши метод $reset()
:
const store = useStore()
store.$reset()
const store = useStore()
store.$reset()
Внутрішньо це викликає функцію state()
для створення нового об'єкта стану і замінює ним поточний стан.
У setup сховищах вам потрібно створити власний метод $reset()
:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
Використання з опційним API
Для наступних прикладів можна вважати, що було створено наступне сховище:
// Приклад шляху до файлу:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
})
// Приклад шляху до файлу:
// ./src/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
})
Якщо ви не використовуєте композиційний API, а використовуєте опційний, то ви можете скористатися допоміжним методом 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
},
}),
},
}
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
},
}),
},
}
Модифікований стан
Якщо ви хочете мати можливість записувати нові значення до цих властивостей стану (наприклад, якщо у вас є форма), ви можете використовувати mapWritableState()
замість цього. Зауважте, що ви не можете передати функцію, як у випадку з 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',
}),
},
}
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
. Він дозволяє застосувати декілька змін одночасно з об'єктом часткового стану:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
Однак, деякі зміни за допомогою цього синтаксису дуже складно або дорого застосувати: будь-яка модифікація колекції (наприклад, додавання, видалення, приєднання елемента до масиву) вимагає створення нової колекції. Через це метод $patch
також приймає функцію для групування таких змін, які важко застосувати за допомогою об'єкта:
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
Основна відмінність полягає в тому, що $patch()
дозволяє вам згрупувати декілька змін в один запис у devtools. Зверніть увагу, що як і прямі зміни стану, так і $patch()
з'являються у devtools і можуть бути перенесені у часі (поки що недоступно у версії Vue 3).
Заміна стану
Ви не можете саме замінити стан сховища, оскільки це порушить реактивність. Однак ви можете його підправити:
// це насправді не замінює `$state`
store.$state = { count: 24 }
// а внутрішньо викликає `$patch()`:
store.$patch({ count: 24 })
// це насправді не замінює `$state`
store.$state = { count: 24 }
// а внутрішньо викликає `$patch()`:
store.$patch({ count: 24 })
Ви також можете встановити початковий стан всього застосунку, змінивши стан
екземпляра pinia
. Це використовується під час SSR для гідратації.
pinia.state.value = {}
pinia.state.value = {}
Підписка на стан
Ви можете спостерігати за станом та його змінами за допомогою методу сховища $subscribe()
, подібно до методу subscribe у Vuex. Перевага використання $subscribe()
над звичайним watch()
полягає в тому, що підписки спрацьовують лише один раз після оновлення (наприклад, при використанні версії функції зверху).
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'прямі зміни' | 'об'єкт з оновленими значеннями' | 'функція з оновленими значеннями'
// теж саме, що й cartStore.$id
mutation.storeId // 'cart'
// доступний лише з mutation.type === 'об'єкт з оновленими значеннями'
mutation.payload // об'єкт з оновленими значеннями передається в cartStore.$patch()
// зберігати весь стан у локальному сховищі щоразу, коли він змінюється
localStorage.setItem('cart', JSON.stringify(state))
})
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'прямі зміни' | 'об'єкт з оновленими значеннями' | 'функція з оновленими значеннями'
// теж саме, що й cartStore.$id
mutation.storeId // 'cart'
// доступний лише з mutation.type === 'об'єкт з оновленими значеннями'
mutation.payload // об'єкт з оновленими значеннями передається в cartStore.$patch()
// зберігати весь стан у локальному сховищі щоразу, коли він змінюється
localStorage.setItem('cart', JSON.stringify(state))
})
За замовчуванням, підписки на стан прив'язуються до компонента, в якому вони були додані (якщо сховище знаходиться в setup()
компонента). Це означає, що вони будуть автоматично видалені, коли компонент буде демонтовано. Якщо ви також хочете зберегти їх після демонтажу компонента, передайте { detached: true }
як другий аргумент, щоб відокремити підписку на стан від поточного компонента:
<script setup>
const someStore = useSomeStore()
// ця підписка буде збережена навіть після того, як компонент буде демонтовано
someStore.$subscribe(callback, { detached: true })
</script>
<script setup>
const someStore = useSomeStore()
// ця підписка буде збережена навіть після того, як компонент буде демонтовано
someStore.$subscribe(callback, { detached: true })
</script>
ПОРАДА
Ви можете спостерігати весь стан екземпляра pinia
за допомогою однієї функції watch()
:
watch(
pinia.state,
(state) => {
// зберігати весь стан у локальному сховищі щоразу, коли він змінюється
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
watch(
pinia.state,
(state) => {
// зберігати весь стан у локальному сховищі щоразу, коли він змінюється
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)