Создание stores

В Quasar, как и в любом приложении на Vue.js, управление состоянием играет ключевую роль в построении масштабируемого и поддерживаемого кода. Stores обеспечивают централизованное хранение данных, их реактивность и удобное взаимодействие между компонентами.


Основы Store

Quasar использует Pinia в качестве рекомендуемого менеджера состояния. Store — это модуль, содержащий:

  • State — реактивное хранилище данных.
  • Getters — вычисляемые свойства для получения данных из state.
  • Actions — методы, изменяющие state или выполняющие асинхронные операции.

Структура типичного store выглядит следующим образом:

import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    id: null,
    name: '',
    email: ''
  }),
  getters: {
    isLoggedIn: (state) => !!state.id
  },
  actions: {
    setUser(user) {
      this.id = user.id;
      this.name = user.name;
      this.email = user.email;
    },
    logout() {
      this.id = null;
      this.name = '';
      this.email = '';
    }
  }
});

State: реактивные данные

State создаётся как функция, возвращающая объект. Важные аспекты:

  • Все свойства state реактивны. Изменение значения автоматически обновляет все компоненты, которые используют эти данные.
  • Для сложных объектов рекомендуется использовать вложенные структуры, но избегать глубокой мутации без действий.

Пример сложного state:

state: () => ({
  cart: [],
  filters: {
    category: 'all',
    priceRange: [0, 100]
  }
})

Изменение cart лучше делать через actions, чтобы сохранить предсказуемость изменений.


Getters: вычисляемые свойства

Getters позволяют создавать вычисляемые свойства, которые зависят от state. Они используются для:

  • Фильтрации данных
  • Агрегации значений
  • Форматирования данных для UI

Пример getter с фильтрацией:

getters: {
  filteredProducts: (state) => (category) => {
    return state.products.filter(p => p.category === category);
  },
  cartTotal: (state) => {
    return state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
  }
}

Геттеры автоматически кешируются и пересчитываются только при изменении зависимостей.


Actions: методы изменения состояния

Actions позволяют изменять state и выполнять асинхронные операции:

  • Доступ к state осуществляется через this.
  • Можно вызывать другие actions внутри того же store.
  • Асинхронные операции (например, запросы к API) удобно выполнять внутри actions.

Пример асинхронного запроса:

actions: {
  async fetchUser(userId) {
    try {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      this.setUser(data);
    } catch (error) {
      console.error('Ошибка загрузки пользователя:', error);
    }
  }
}

Организация нескольких Stores

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

  • userStore — данные о пользователе и аутентификации
  • cartStore — корзина и покупки
  • productStore — каталог товаров и фильтры

Stores можно комбинировать в компонентах:

import { useUserStore } from 'stores/userStore';
import { useCartStore } from 'stores/cartStore';

const userStore = useUserStore();
const cartStore = useCartStore();

console.log(userStore.isLoggedIn);
console.log(cartStore.cartTotal);

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

Quasar обеспечивает прямую интеграцию Pinia, что позволяет легко использовать store в компонентах:

<template>
  <div>
    <h2>Добро пожаловать, {{ userStore.name }}</h2>
    <p>Товаров в корзине: {{ cartStore.cart.length }}</p>
  </div>
</template>

<script setup>
import { useUserStore } from 'stores/userStore';
import { useCartStore } from 'stores/cartStore';

const userStore = useUserStore();
const cartStore = useCartStore();
</script>

Преимущества такого подхода:

  • Компоненты становятся тонкими, без локального состояния, которое сложно отслеживать.
  • Любые изменения state автоматически отражаются в интерфейсе.
  • Повышается масштабируемость приложения за счёт модульности.

Реактивность и отслеживание изменений

Pinia обеспечивает полную реактивность:

  • Любое изменение свойства state через actions мгновенно обновляет все зависимости.
  • Использование computed с getters позволяет создавать цепочки реактивных зависимостей.

Пример реактивного наблюдения:

import { watch } from 'vue';

watch(
  () => userStore.isLoggedIn,
  (newValue) => {
    console.log('Статус входа изменился:', newValue);
  }
);

Сохраняемость состояния (Persistence)

Для длительного хранения данных между перезагрузками можно использовать plugins Pinia:

import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

Пример сохранения корзины:

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

Модули и типизация

  • Для TypeScript рекомендуется явно типизировать state, getters и actions.
  • Для больших проектов store можно разбивать на модули, что облегчает поддержку и тестирование.

Пример типизации state:

interface UserState {
  id: number | null;
  name: string;
  email: string;
}

export const useUserStore = defineStore<'user', UserState>({
  state: (): UserState => ({
    id: null,
    name: '',
    email: ''
  })
});

Эта структура позволяет строить масштабируемые, реактивные и предсказуемые приложения, полностью используя возможности Quasar и Pinia для управления состоянием.