[К содержанию](../readme.md#введение-в-web-разработку) # Управление состоянием (pinia) ## Что такое управление состоянием?​ Технически каждый экземпляр компонента **Vue** уже "управляет" своим реактивным состоянием. Возьмём для примера простой компонент счетчика: ```vue ``` Это самостоятельный блок, состоящий из следующих частей: * **Состояние** - источник данных, который управляет нашим приложением; * **Представление** - декларативное отображение состояния; * **Действия** - возможные способы изменения состояния в ответ на пользовательский ввод из представления. Это простое представление концепции "одностороннего потока данных": ![](../img/web_024.png) Однако эта простота начинает нарушаться, когда у нас есть **несколько компонентов, имеющих общее состояние**: 1. Несколько представлений могут зависеть от одного и того же фрагмента состояния. 1. Действиям из разных представлений может потребоваться мутировать один и тот же фрагмент состояния. В первом случае возможным обходным путем является "поднятие" общего состояния до общего компонента-предка, а затем передача его вниз в виде входных параметров. Однако это быстро становится утомительным в деревьях компонентов с глубокой иерархией, что приводит к другой проблеме, известной как **пробрасывание входных параметров (prop drilling)**. Во втором случае мы часто прибегаем к таким решениям, как обращение к прямым родительским и дочерним экземплярам через ссылки на элементы шаблона или попытка мутировать и синхронизировать несколько копий состояния через генерируемые события. Оба эти паттерна являются хрупкими и быстро приводят к сложно поддерживаемому коду. Более простым и понятным решением является извлечение общего состояния из компонентов и управление им в глобальном синглтоне. Таким образом, наше дерево компонентов превращается в большое "представление", и любой компонент может получить доступ к состоянию или вызвать действия, независимо от того, где он находится в дереве! **Pinia** - это библиотека для создания хранилища для Vue, которая позволяет вам совместно использовать состояние между компонентами/страницами. ## Что такое хранилище? Хранилище (как, например, Pinia) - это сущность, которая содержит состояние и бизнес-логику, не привязанную к дереву компонентов. Другими словами, она содержит глобальное состояние. Это немного похоже на компонент, и у которого все могут читать и записывать данные. В нем есть три основных концепции: [состояние (state)](https://pinia-ru.netlify.app/core-concepts/state.html), [геттеры (getters)](https://pinia-ru.netlify.app/core-concepts/getters.html) и [действия (actions)](https://pinia-ru.netlify.app/core-concepts/actions.html), и можно с уверенностью предположить, что эти концепции эквивалентны `data`, `computed` и `methods` в компонентах (про эти концепции вы слышали в лекциях про **vue2**, во **vue3** их уже нет). ## Начало работы с Pinia ### Установка При создании SPA приложения вы должны были установить и pinia, но если не установили, то всегда можно добавить [вручную](https://pinia-ru.netlify.app/getting-started.html#instalation) ## [Определение хранилища](https://pinia-ru.netlify.app/core-concepts/#defining-a-store) **Pinia** поддерживает два варианта синтаксиса: [option](https://pinia-ru.netlify.app/core-concepts/#option-stores) и [setup](https://pinia-ru.netlify.app/core-concepts/#setup-stores). Принципиальной разницы между ними нет, синтаксис аналогичен таким же наименованиям во **VUE**, поэтому далее мы будем использовать вариант **setup**. В "рыбе" нашего **SPA** приложения уже есть пример хранилища (`src/stores/counters.js`): ```js import { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', () => { const count = ref(0) const doubleCount = computed(() => count.value * 2) function increment() { count.value++ } return { count, doubleCount, increment } }) ``` Как видите, используются уже знакомые вам концепции _реактивных переменных_ (_ref_), _вычисляемых переменных_ (_computed_) и методов (_function_). ## [Использование хранилища](https://pinia-ru.netlify.app/core-concepts/#using-the-store) Для подключения хранилища к компоненту используется обычный импорт: ```vue ``` Для получения значений свойств хранилища или вызыва его методов используйте _store_ как обычный объект ```js const value = store.count store.increment() ``` Но надо учитывать, что свойства этого объекта не реактивны. Чтобы извлечь свойства из хранилища, сохраняя их реактивность, можно использовать метод _storeToRefs()_. Он создаст **ref**-ссылки для каждого реактивного свойства. Это полезно, когда вы используете только состояние из хранилища, но не вызываете никакие действия. Обратите внимание, что вы также можете деструктурировать действия напрямую из хранилища, так как они также привязаны к самому хранилищу: ```vue ``` ## Использование **localStorage** для хранения данных при перезапуске приложения В браузере есть два хранилища типа "ключ" - "значение" ("ключ" - строка, "значение" - скалярные данные (целое, строка и т.п.)): * **localStorage** - постоянное хранилище (работает всегда) * **sessionStorage** - временное хранилище, пропадает при закрытии браузера Например, если нам нужно сохранить данные авторизации, то мы можем использовать **localStorage**: ```js export const useApiStore = defineStore('api', () => { const authData = ref(null) const isAuthenticated = computed(() => authData.value != null) // считываем данные из localStorage const storedAuthData = localStorage.getItem('authData') // если там что-то есть if (storedAuthData != null) { // десериализуем данные в нужный объект authData.value = JSON.parse(storedAuthData) } function setAuthData (newAuthData) { // при сохранении сериализуем (преобразуем в строку) localStorage.setItem( 'authData', JSON.stringify(newAuthData) ) } return { authData, isAuthenticated, setAuthData } }) ``` [Назад](./web_16.md) | [Дальше](./web_18.md)