Custom stores

В SvelteKit хранилища (stores) представляют собой реактивные объекты, позволяющие централизованно управлять состоянием приложения и делиться им между компонентами. Стандартные хранилища — это writable, readable и derived. Кастомные хранилища (custom stores) позволяют создавать более сложные структуры с собственной логикой изменения состояния и вспомогательными методами.


Создание кастомного хранилища

Кастомное хранилище строится на основе функции, которая возвращает объект с методами subscribe и дополнительными функциями для управления состоянием. Простейший шаблон выглядит так:

import { writable } from 'svelte/store';

function createCounter() {
  const { subscribe, set, upd ate } = writable(0);

  return {
    subscribe,
    increment: () => upd ate(n => n + 1),
    decrement: () => upd ate(n => n - 1),
    reset: () => se t(0)
  };
}

export const counter = createCounter();

Разбор ключевых моментов:

  • subscribe — обязательный метод, позволяющий компонентам подписываться на изменения состояния.
  • set и update — стандартные методы writable для изменения значения. set устанавливает новое значение, update получает текущее значение и возвращает обновлённое.
  • Методы increment, decrement, reset — примеры расширения стандартного функционала, инкапсулируя логику внутри хранилища.

Расширение логики через кастомные методы

Кастомные хранилища позволяют добавлять сложные операции с состоянием:

function createTodoStore() {
  const { subscribe, se t, upd ate } = writable([]);

  return {
    subscribe,
    add: (todo) => upd ate(todos => [...todos, todo]),
    remove: (id) => upd ate(todos => todos.filter(t => t.id !== id)),
    toggle: (id) => update(todos =>
      todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
    ),
    reset: () => se t([])
  };
}

export const todos = createTodoStore();

Преимущество такого подхода — инкапсуляция логики работы с массивом задач и централизованное управление всеми изменениями состояния.


Подписка и реактивность

Компоненты могут подписываться на кастомное хранилище стандартным образом:

<script>
  import { counter } from './stores.js';

  let count;
  const unsubscribe = counter.subscribe(value => count = value);

  // Можно отписаться при необходимости
  // onDestroy(() => unsubscribe());
</script>

<button on:click={counter.increment}>+</button>
<button on:click={counter.decrement}>-</button>
<p>Текущее значение: {count}</p>

Подписка через subscribe автоматически обеспечивает реактивное обновление данных при изменении состояния хранилища.


Использование derived с кастомными хранилищами

Кастомные хранилища могут быть источником для вычисляемых (derived) значений:

import { derived } from 'svelte/store';
import { todos } from './stores.js';

export const completedTodos = derived(todos, $todos =>
  $todos.filter(todo => todo.completed)
);

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


Асинхронные операции и кастомные хранилища

Кастомные хранилища удобны для работы с асинхронными запросами:

function createUserStore() {
  const { subscribe, se t } = writable({ user: null, loading: false });

  return {
    subscribe,
    fetchUser: async (id) => {
      se t({ user: null, loading: true });
      const res = await fetch(`/api/users/${id}`);
      const user = await res.json();
      se t({ user, loading: false });
    }
  };
}

export const userStore = createUserStore();

Преимущества:

  • Управление состоянием загрузки внутри одного хранилища.
  • Изоляция логики fetch-запроса.
  • Автоматическое обновление подписчиков при изменении данных.

Инкапсуляция и масштабируемость

Кастомные хранилища позволяют:

  • Ограничивать прямой доступ к состоянию через публичные методы.
  • Создавать сложные бизнес-логики внутри хранилища, избегая дублирования кода в компонентах.
  • Легко тестировать логику состояния отдельно от UI.

Поддержка нескольких подписчиков и реактивность компонентов

Каждое кастомное хранилище поддерживает множественные подписки:

const unsubscribe1 = counter.subscribe(val => console.log('Subscriber 1:', val));
const unsubscribe2 = counter.subscribe(val => console.log('Subscriber 2:', val));

Изменения, внесённые через методы хранилища, синхронно уведомляют всех подписчиков, обеспечивая согласованное состояние во всех компонентах приложения.


Итоговые принципы работы с кастомными хранилищами

  1. Обязательная реализация subscribe — ключ к реактивности.
  2. Методы для изменения состояния (set, update, кастомные функции) инкапсулируют логику.
  3. Совместимость с derived и асинхронными операциями делает их универсальными.
  4. Инкапсуляция логики повышает читаемость и масштабируемость приложения.

Кастомные хранилища — это не просто обёртка над writable, а мощный инструмент для создания структурированного и реактивного состояния в SvelteKit-приложениях.