Стан
Стан - це, здебільшого, центральна частина вашого сховища. Люди часто починають з визначення стану, який представляє їхній додаток. У 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 }
)