Compound stores

Compound stores — это расширение стандартной концепции stores в SvelteKit, позволяющее объединять несколько реактивных состояний в одну логическую единицу. В отличие от обычного writable или readable store, compound store предоставляет структуру для работы с комплексными данными, сохраняя реактивность и облегчая управление состоянием на уровне нескольких связанных значений.

Ключевая особенность — агрегация нескольких под-stores и возможность централизованного управления ими через единый интерфейс.


Создание Compound Store

Для создания compound store можно использовать комбинацию стандартных writable и derived store. Основная идея — инкапсулировать логику работы с несколькими store в одном объекте:

import { writable, derived } from 'svelte/store';

function createUserStore() {
  const name = writable('');
  const age = writable(0);
  const isAdult = derived(age, $age => $age >= 18);

  return {
    name,
    age,
    isAdult,
    setName: (newName) => name.set(newName),
    setAge: (newAge) => age.set(newAge),
    reset: () => {
      name.set('');
      age.set(0);
    }
  };
}

export const userStore = createUserStore();

Разбор:

  • name и age — базовые writable stores.
  • isAdultderived store, который автоматически пересчитывается при изменении age.
  • Методы setName, setAge и reset инкапсулируют логику управления состоянием, сохраняя интерфейс store чистым и предсказуемым.

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

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

$userStore.name; // доступ к значению name
$userStore.isAdult.subscribe(value => {
  console.log('Пользователь совершеннолетний:', value);
});

Можно также подписываться на весь compound store через вспомогательную функцию:

import { derived } from 'svelte/store';

const userData = derived(
  [userStore.name, userStore.age],
  ([$name, $age]) => ({ name: $name, age: $age })
);

userData.subscribe(data => console.log(data));

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


Управление сложными структурами данных

Compound stores особенно эффективны при работе с объектами и массивами, где требуется поддержка реактивности на вложенных уровнях. Пример — управление списком задач:

function createTodoStore() {
  const todos = writable([]);

  const addTodo = (text) => {
    todos.update(current => [...current, { text, done: false }]);
  };

  const toggleTodo = (index) => {
    todos.update(current =>
      current.map((todo, i) =>
        i === index ? { ...todo, done: !todo.done } : todo
      )
    );
  };

  const removeTodo = (index) => {
    todos.update(current => current.filter((_, i) => i !== index));
  };

  const completedCount = derived(todos, $todos =>
    $todos.filter(todo => todo.done).length
  );

  return {
    todos,
    addTodo,
    toggleTodo,
    removeTodo,
    completedCount
  };
}

export const todoStore = createTodoStore();

Особенности:

  • Любое изменение в массиве или объекте автоматически отражается в derived свойствах.
  • Методы инкапсулируют логику, избегая дублирования кода в компонентах.

Интеграция с компонентами Svelte

Compound store легко интегрируется в компоненты:

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

  let newTodo = '';
</script>

<input bind:value={newTodo} placeholder="Добавить задачу" />
<button on:click={() => { todoStore.addTodo(newTodo); newTodo = ''; }}>
  Добавить
</button>

<ul>
  {#each $todoStore.todos as todo, index}
    <li>
      <input type="checkbox" bind:checked={todo.done} on:change={() => todoStore.toggleTodo(index)} />
      {todo.text}
      <button on:click={() => todoStore.removeTodo(index)}>Удалить</button>
    </li>
  {/each}
</ul>

<p>Выполнено: {$todoStore.completedCount}</p>

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

  • Логика состояния сосредоточена в store, компоненты остаются чистыми.
  • Реактивность автоматически распространяется на все связанные свойства.
  • Упрощается тестирование и переиспользование store в разных компонентах.

Советы по проектированию Compound Stores

  1. Инкапсуляция Всегда предоставлять публичный интерфейс для управления состоянием через методы. Не раскрывать внутренние writable без необходимости.

  2. Разделение логики Для сложных объектов разбивать store на под-store и объединять через derived или вспомогательные функции.

  3. Оптимизация производительности Минимизировать подписки на массивы и объекты целиком, использовать derived для вычислений и агрегатов.

  4. Тестируемость Compound store легко тестировать вне компонентов Svelte, что повышает надежность приложения.

  5. Согласованность реактивности Вложенные объекты должны обновляться через update или создание новых копий (spread operator), чтобы сохранялась реактивность Svelte.


Compound stores обеспечивают структурированное и реактивное управление сложными состояниями, делая SvelteKit приложения более масштабируемыми, удобными в сопровождении и тестируемыми.