Создание stores

В Vue.js концепция store реализуется через библиотеку Pinia, которая пришла на смену Vuex начиная с Vue 3. Store представляет собой централизованное хранилище состояния приложения, позволяющее управлять данными между компонентами без необходимости передачи их через пропсы или события.


Создание и регистрация store

Для создания store используется функция defineStore. Она принимает два обязательных параметра:

  1. Идентификатор id — уникальное имя хранилища.
  2. Объект с настройками — содержит состояние, геттеры и действия.

Пример структуры:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    history: []
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
      this.history.push({ action: 'increment', value: this.count })
    },
    decrement() {
      this.count--
      this.history.push({ action: 'decrement', value: this.count })
    }
  }
})

Ключевые моменты:

  • state — функция, возвращающая объект с начальными данными.
  • getters — вычисляемые свойства, которые зависят от состояния.
  • actions — методы для изменения состояния. Они могут быть синхронными и асинхронными.

Использование store в компонентах

После создания хранилища его можно подключить в любом компоненте с помощью вызова функции useStore:

<template>
  <div>
    <p>Счетчик: {{ counter.count }}</p>
    <p>Двойной счетчик: {{ counter.doubleCount }}</p>
    <button @click="counter.increment">Увеличить</button>
    <button @click="counter.decrement">Уменьшить</button>
  </div>
</template>

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

const counter = useCounterStore()
</script>

Особенности использования:

  • Состояние state доступно напрямую через свойства объекта store.
  • Методы из actions вызываются через store.methodName().
  • getters можно использовать как обычные свойства объекта.

Асинхронные действия в store

Actions поддерживают асинхронные операции, что позволяет выполнять запросы к API или работать с таймерами:

actions: {
  async fetchData() {
    try {
      const response = await fetch('https://api.example.com/data')
      const data = await response.json()
      this.history.push({ action: 'fetch', data })
    } catch (error) {
      console.error('Ошибка при получении данных:', error)
    }
  }
}

Композиция нескольких store

В больших приложениях рекомендуется разделять состояние на отдельные store по функциональным модулям. Например:

  • userStore — управление пользователями и авторизацией.
  • productStore — данные товаров.
  • cartStore — корзина и покупки.

Компоненты могут одновременно подключать несколько store и использовать их независимо друг от друга:

<script setup>
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'

const user = useUserStore()
const cart = useCartStore()
</script>

Подписка на изменения состояния

Pinia позволяет реагировать на изменения данных с помощью метода $subscribe:

const counter = useCounterStore()

counter.$subscribe((mutation, state) => {
  console.log('Изменение состояния:', mutation, state)
})

Параметры функции обратного вызова:

  • mutation — объект с информацией о том, какое действие вызвало изменение.
  • state — актуальное состояние store после мутации.

Сохранение состояния между сессиями

Для того чтобы состояние store сохранялось после перезагрузки страницы, используется плагин pinia-plugin-persistedstate:

import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: []
  }),
  actions: {
    addItem(item) {
      this.items.push(item)
    }
  },
  persist: true
})

После включения persist: true данные будут храниться в localStorage и автоматически подгружаться при следующем запуске приложения.


Рекомендации по структуре store

  • Минимизировать глобальное состояние. Сохранять только данные, которые реально нужны нескольким компонентам.
  • Использовать getters для вычислений. Не дублировать вычисляемые значения в state.
  • Делать действия атомарными. Каждое действие должно выполнять одну конкретную задачу.
  • Разделять store по логическим модулям. Упрощает поддержку и тестирование.

Интеграция с TypeScript

Pinia полностью поддерживает TypeScript. Типизация состояния и действий позволяет предотвратить ошибки на этапе компиляции:

interface CounterState {
  count: number
  history: { action: string, value: number }[]
}

export const useCounterStore = defineStore<'counter', CounterState>({
  id: 'counter',
  state: (): CounterState => ({
    count: 0,
    history: []
  }),
  getters: {
    doubleCount: (state) => state.count * 2
  },
  actions: {
    increment() {
      this.count++
    }
  }
})

Store в Vue.js с Pinia обеспечивает мощный и гибкий инструмент для управления состоянием приложения, позволяя создавать масштабируемые, модульные и легко поддерживаемые проекты.