web_17.md 10 KB

К содержанию

Управление состоянием (pinia)

Что такое управление состоянием?​

Технически каждый экземпляр компонента Vue уже "управляет" своим реактивным состоянием. Возьмём для примера простой компонент счетчика:

<script setup>
import { ref } from 'vue'

// состояние
const count = ref(0)

// действия
function increment() {
  count.value++
}
</script>

<!-- представление -->
<template>{{ count }}</template>

Это самостоятельный блок, состоящий из следующих частей:

  • Состояние - источник данных, который управляет нашим приложением;
  • Представление - декларативное отображение состояния;
  • Действия - возможные способы изменения состояния в ответ на пользовательский ввод из представления.

Это простое представление концепции "одностороннего потока данных":

Однако эта простота начинает нарушаться, когда у нас есть несколько компонентов, имеющих общее состояние:

  1. Несколько представлений могут зависеть от одного и того же фрагмента состояния.
  2. Действиям из разных представлений может потребоваться мутировать один и тот же фрагмент состояния.

В первом случае возможным обходным путем является "поднятие" общего состояния до общего компонента-предка, а затем передача его вниз в виде входных параметров. Однако это быстро становится утомительным в деревьях компонентов с глубокой иерархией, что приводит к другой проблеме, известной как пробрасывание входных параметров (prop drilling).

Во втором случае мы часто прибегаем к таким решениям, как обращение к прямым родительским и дочерним экземплярам через ссылки на элементы шаблона или попытка мутировать и синхронизировать несколько копий состояния через генерируемые события. Оба эти паттерна являются хрупкими и быстро приводят к сложно поддерживаемому коду.

Более простым и понятным решением является извлечение общего состояния из компонентов и управление им в глобальном синглтоне. Таким образом, наше дерево компонентов превращается в большое "представление", и любой компонент может получить доступ к состоянию или вызвать действия, независимо от того, где он находится в дереве!

Pinia - это библиотека для создания хранилища для Vue, которая позволяет вам совместно использовать состояние между компонентами/страницами.

Что такое хранилище?

Хранилище (как, например, Pinia) - это сущность, которая содержит состояние и бизнес-логику, не привязанную к дереву компонентов. Другими словами, она содержит глобальное состояние. Это немного похоже на компонент, и у которого все могут читать и записывать данные. В нем есть три основных концепции: состояние (state), геттеры (getters) и действия (actions), и можно с уверенностью предположить, что эти концепции эквивалентны data, computed и methods в компонентах (про эти концепции вы слышали в лекциях про vue2, во vue3 их уже нет).

Начало работы с Pinia

Установка

При создании SPA приложения вы должны были установить и pinia, но если не установили, то всегда можно добавить вручную

Определение хранилища

Pinia поддерживает два варианта синтаксиса: option и setup.

Принципиальной разницы между ними нет, синтаксис аналогичен таким же наименованиям во VUE, поэтому далее мы будем использовать вариант setup.

В "рыбе" нашего SPA приложения уже есть пример хранилища (src/stores/counters.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).

Использование хранилища

Для подключения хранилища к компоненту используется обычный импорт:

<script setup>
import { useCounterStore } from 'src/stores/counter'

// доступ к переменной `store` в любом месте компонента
const store = useCounterStore()
</script>

Для получения значений свойств хранилища или вызыва его методов используйте store как обычный объект

const value = store.count
store.increment()

Но надо учитывать, что свойства этого объекта не реактивны.

Чтобы извлечь свойства из хранилища, сохраняя их реактивность, можно использовать метод storeToRefs(). Он создаст ref-ссылки для каждого реактивного свойства. Это полезно, когда вы используете только состояние из хранилища, но не вызываете никакие действия. Обратите внимание, что вы также можете деструктурировать действия напрямую из хранилища, так как они также привязаны к самому хранилищу:

<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'

const store = useCounterStore()
// `name` и `doubleCount` являются реактивными ref-ссылками
// При этом также будут извлечены ref-ссылки на свойства, добавленные плагинами
// но будет пропущено любое действие или нереактивное (не ref/reactive) свойство
const { name, doubleCount } = storeToRefs(store)
// действие increment может быть просто деструктурировано
const { increment } = store
</script>

Использование localStorage для хранения данных при перезапуске приложения

В браузере есть два хранилища типа "ключ" - "значение" ("ключ" - строка, "значение" - скалярные данные (целое, строка и т.п.)):

  • localStorage - постоянное хранилище (работает всегда)
  • sessionStorage - временное хранилище, пропадает при закрытии браузера

Например, если нам нужно сохранить данные авторизации, то мы можем использовать localStorage:

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

Назад | Дальше