Глобальное состояние с использованием stores

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

Что такое stores?

Store в Svelte представляет собой объект, который хранит данные и предоставляет механизмы для подписки и обновления этих данных. Подписчики получают уведомления, когда данные в store изменяются, что позволяет автоматически обновлять компоненты, использующие эти данные. Это даёт возможность эффективно управлять состоянием приложения, не прибегая к сторонним библиотекам для работы с глобальным состоянием.

Svelte предоставляет несколько типов store, каждый из которых имеет свою сферу применения: writable, readable и derived.

Типы store

Writable store

Наиболее часто используемый тип store — writable. Этот store позволяет не только читать данные, но и изменять их.

Для создания writable store используется функция writable():

import { writable } from 'svelte/store';

const count = writable(0);

В примере выше создаётся store с начальным значением 0. Для изменения значения этого store используется метод .set():

count.set(5); // устанавливаем новое значение

Кроме того, можно использовать метод .update(), который позволяет изменять состояние на основе его текущего значения:

count.update(n => n + 1); // увеличиваем значение на 1

Чтобы подписаться на изменения значения store, можно использовать конструкцию $store внутри компонента:

<script>
  import { count } from './store.js';
</script>

<p>Значение: {$count}</p>
<button on:click={() => count.update(n => n + 1)}>Увеличить</button>

В этом примере значение count автоматически обновляется при каждом изменении.

Readable store

Readable store используется, когда состояние данных доступно только для чтения, но не для записи. Это может быть полезно, когда нужно предоставить данные для использования в разных частях приложения, но не разрешать изменять их.

Для создания readable store используется функция readable():

import { readable } from 'svelte/store';

const time = readable(new Date(), set => {
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return () => clearInterval(interval);
});

В этом примере time обновляется каждую секунду с текущим временем. Вызываемая функция set используется для установки нового значения, а возврат из функции, переданной в readable(), устанавливает очистку интервала при уничтожении компонента.

Подписка на time в компоненте будет аналогична подписке на любой другой store:

<script>
  import { time } from './store.js';
</script>

<p>Текущее время: {$time}</p>

Derived store

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

Для создания derived store используется функция derived():

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

const doubledCount = derived(count, $count => $count * 2);

Здесь doubledCount будет автоматически обновляться при изменении count, и всегда будет в два раза больше значения count. Подписка на derived store осуществляется так же, как и на другие store:

<script>
  import { doubledCount } from './store.js';
</script>

<p>Удвоенное значение: {$doubledCount}</p>

Writable и другие методы

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

  • subscribe() — позволяет подписаться на store вручную. Этот метод возвращает функцию отписки:

    const unsubscribe = count.subscribe(value => {
      console.log(value);
    });
    
    // Отписка от store
    unsubscribe();
  • set() — для установки нового значения в writable store.

  • update() — для обновления значения на основе текущего состояния.

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

Для использования глобальных stores в компонентах, Svelte автоматически отслеживает изменения и обновляет DOM. Чтобы извлечь данные из store, достаточно использовать синтаксис $store, где store — это название вашего хранимого объекта.

Пример использования writable store:

<script>
  import { count } from './store.js';
</script>

<p>Счёт: {$count}</p>
<button on:click={() => count.update(n => n + 1)}>Увеличить</button>

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

Для использования других типов store, например, readable или derived, синтаксис остаётся аналогичным. Главное отличие состоит в том, что вы не можете изменять данные в readable store или store, полученном через derived.

Организация состояния с помощью stores

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

Пример:

// store.js
import { writable } from 'svelte/store';

export const user = writable({
  name: 'Иван',
  email: 'ivan@example.com'
});

Затем в любом компоненте можно подписаться на данные этого store:

<script>
  import { user } from './store.js';
</script>

<p>Имя: {$user.name}</p>
<p>Электронная почта: {$user.email}</p>

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

Заключение

Использование stores в Svelte значительно упрощает управление состоянием в сложных приложениях. Это не только снижает необходимость в дополнительных библиотеках для глобального состояния, но и предоставляет мощные и удобные механизмы для работы с реактивными данными, улучшая производительность и читаемость кода.