No history yet

Управление состоянием

Управление состоянием в Svelte

Когда приложение растет, передача данных через свойства (props) от родительского компонента к дочернему становится громоздкой. Это явление называют «пробросом свойств» (prop drilling). Чтобы избежать этого и сделать управление данными более чистым и эффективным, в Svelte есть два мощных инструмента: хранилища (stores) и контекст (context).

Хранилища для глобального состояния

Хранилище — это объект с методом subscribe, который позволяет компонентам получать уведомления об изменениях значения. Представьте его как центральный источник данных, на который может «подписаться» любой компонент в приложении.

В Svelte есть несколько видов встроенных хранилищ.

writable (записываемое) хранилище: Самый распространенный тип. Его значение можно изменять как изнутри, так и снаружи компонента. Для этого используются методы set (установить новое значение) и update (обновить на основе текущего).

import { writable } from 'svelte/store';

// Создаем хранилище со значением по умолчанию 0
export const count = writable(0);

Чтобы использовать это хранилище в компоненте, мы импортируем его и подписываемся на изменения. Самый простой способ сделать это — использовать префикс $.

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

  function increment() {
    // $count — это сокращение для получения значения
    // и автоматической подписки/отписки.
    // Мы можем присваивать ему значение напрямую.
    $count += 1;
  }
</script>

<h1>Счетчик: {$count}</h1>
<button on:click={increment}>+</button>

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

readable (читаемое) хранилище: Это хранилище, значение которого нельзя изменить извне. Оно устанавливается при создании и может обновляться только изнутри. Это полезно для данных, которые должны быть защищены от случайных изменений, например, таймер или данные от веб-сокета.

import { readable } from 'svelte/store';

export const time = readable(new Date(), function start(set) {
  // Эта функция выполняется, когда появляется первый подписчик
  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  // Эта функция выполняется, когда уходит последний подписчик
  return function stop() {
    clearInterval(interval);
  };
});

derived (производное) хранилище: Производное хранилище получает свое значение от одного или нескольких других хранилищ. Оно полезно, когда нужно вычислить какое-то значение на основе другого состояния, не создавая лишней логики в компонентах.

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

const startTime = readable(new Date().getTime());

export const elapsed = derived(
  startTime,
  $startTime => Math.round((new Date().getTime() - $startTime) / 1000)
);
// elapsed будет автоматически обновляться, но мы не можем
// его обновить напрямую. В этом примере оно не будет меняться,
// т.к. startTime не меняется. Но если бы startTime было writable,
// elapsed бы обновлялось при каждом изменении startTime.

Контекст для иерархии компонентов

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

В отличие от хранилищ, контекст не является реактивным. Если значение в контексте изменится, компоненты, которые его используют, не обновятся автоматически. Поэтому контекст лучше всего подходит для передачи данных, которые не меняются, например, функций или объектов с настройками.

Для работы с контекстом используются две функции: setContext и getContext.

<!-- Parent.svelte -->
<script>
  import { setContext } from 'svelte';
  import Child from './Child.svelte';

  const someData = {
    user: 'Alex',
    theme: 'dark'
  };

  // Устанавливаем контекст. Ключ 'app-data' — любой уникальный идентификатор.
  setContext('app-data', someData);
</script>

<Child />

Любой дочерний компонент внутри Parent.svelte, даже если он вложен на несколько уровней, может получить доступ к этим данным.

<!-- DeeplyNestedChild.svelte -->
<script>
  import { getContext } from 'svelte';

  // Получаем данные по тому же ключу
  const appData = getContext('app-data');
</script>

<p>Пользователь: {appData.user}</p>
<p>Тема: {appData.theme}</p>

Основное правило: используйте хранилища для глобального, реактивного состояния, а контекст — для передачи статических данных или функций внутри определенной иерархии компонентов.

Интеграция с внешними библиотеками

Хотя встроенные инструменты Svelte покрывают большинство потребностей, иногда проект требует интеграции с более сложными решениями, такими как XState для конечных автоматов или Immer для работы с иммутабельными данными.

Благодаря гибкости Svelte, интегрировать их довольно просто. Часто для этого достаточно обернуть логику внешней библиотеки в кастомное Svelte-хранилище.

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

import { readable } from 'svelte/store';
import { createMachine, interpret } from 'xstate';

const machine = createMachine(/* ...конфигурация машины... */);

export const myService = readable(machine.initialState, set => {
  const service = interpret(machine).onTransition(state => {
    if (state.changed) {
      set(state);
    }
  });

  service.start();

  return () => service.stop();
});

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

Используйте систему хранилищ Svelte для элегантной обработки переходов состояний без шаблонного кода.

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

Quiz Questions 1/6

Какая основная проблема в больших приложениях Svelte решается с помощью хранилищ (stores) и контекста (context)?

Quiz Questions 2/6

Какое ключевое отличие контекста (context) от хранилища (store) в Svelte?

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