Программное управление состоянием

В библиотеке Carbon Components Svelte управление состоянием компонентов построено с учётом реактивности Svelte, что позволяет создавать динамичные интерфейсы без необходимости внедрения сложных сторонних менеджеров состояния. Основной механизм — использование реактивных переменных, store и событий компонентов.

Реактивные переменные

Любая переменная, объявленная с ключевым словом let в Svelte-компоненте, автоматически становится реактивной. Изменение её значения вызывает обновление интерфейса. В Carbon Components Svelte это используется для управления состоянием таких элементов, как Checkbox, RadioButton, TextInput и других.

<script>
  import { Checkbox } from 'carbon-components-svelte';
  let checked = false;
</script>

<Checkbox bind:checked>
  Включить уведомления
</Checkbox>

<p>{checked ? 'Уведомления включены' : 'Уведомления выключены'}</p>

Ключевые моменты:

  • Использование bind: связывает внутреннее состояние компонента с переменной.
  • Любые изменения checked автоматически отражаются в интерфейсе.

Store для глобального состояния

Для управления состоянием между компонентами используется Svelte store. Carbon Components Svelte полностью совместим с writable, readable и derived stores.

<script>
  import { writable } from 'svelte/store';
  import { Switch } from 'carbon-components-svelte';

  export const darkMode = writable(false);
</script>

<Switch bind:checked={$darkMode} labelText="Темная тема" />

<p>{$darkMode ? 'Тема: тёмная' : 'Тема: светлая'}</p>

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

  • $darkMode — это реактивная подписка на store.
  • Изменения состояния в любом компоненте автоматически обновляют все связанные элементы.
  • Использование writable обеспечивает возможность записи, readable — только чтение, derived — вычисляемые значения на основе других store.

События компонентов

Большинство компонентов Carbon Components Svelte генерируют кастомные события, которые позволяют отслеживать пользовательские действия и изменять состояние. Пример для TextInput:

<script>
  import { TextInput } from 'carbon-components-svelte';
  let inputValue = '';

  function handleInput(event) {
    inputValue = event.detail.value;
  }
</script>

<TextInput
  bind:value={inputValue}
  on:input={handleInput}
  labelText="Имя пользователя"
/>

<p>Вы ввели: {inputValue}</p>

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

  • event.detail содержит данные, специфичные для компонента.
  • bind:value автоматически обновляет переменную, поэтому обработчик нужен только при дополнительной логике.

Состояние сложных компонентов

Для компонентов вроде Modal, Dropdown и Tabs управление состоянием обычно включает несколько переменных:

  • Modal: open (boolean), size (string)
  • Dropdown: selectedItem, open
  • Tabs: selected (индекс активного таба)
<script>
  import { Modal } from 'carbon-components-svelte';
  let showModal = false;
</script>

<button on:click={() => showModal = true}>Открыть модальное окно</button>

<Modal bind:open={showModal} modalLabel="Пример модального окна">
  <p>Содержимое модального окна</p>
</Modal>

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

  • Использование bind:open позволяет закрывать модальное окно как через кнопку, так и программно.
  • Можно добавлять реактивные действия при открытии/закрытии через $: if (showModal) { ... }.

Композиция состояния

Для более сложных интерфейсов состояние часто распределяется между несколькими store и локальными переменными. При этом применяются следующие подходы:

  • Поддержка одного источника истины: центральный store управляет всеми взаимосвязанными компонентами.
  • Реактивные вычисления: derived store позволяет вычислять значения на основе нескольких источников состояния.
  • Локальное и глобальное сочетание: модальные окна, переключатели и табы могут использовать локальные переменные, а данные формы и фильтры — глобальные store.

Пример сложного состояния

<script>
  import { writable, derived } from 'svelte/store';
  import { Checkbox } from 'carbon-components-svelte';

  const tasks = writable([
    { id: 1, text: 'Сделать отчёт', done: false },
    { id: 2, text: 'Обновить сайт', done: true }
  ]);

  const completedTasks = derived(tasks, $tasks =>
    $tasks.filter(task => task.done)
  );

  function toggleTask(id) {
    tasks.update(list =>
      list.map(task => task.id === id ? {...task, done: !task.done} : task)
    );
  }
</script>

{#each $tasks as task (task.id)}
  <Checkbox
    bind:checked={task.done}
    on:change={() => toggleTask(task.id)}
  >
    {task.text}
  </Checkbox>
{/each}

<p>Выполнено задач: {$completedTasks.length}</p>

Ключевые моменты:

  • derived store позволяет автоматически подсчитывать количество выполненных задач.
  • Использование update и map обеспечивает иммутабельность данных при изменении состояния.
  • Связывание Checkbox с объектом задачи требует отдельной функции, так как нельзя напрямую bind:checked на свойство объекта из store без копирования.

Рекомендации по управлению состоянием

  • Для простых компонентов используйте локальные реактивные переменные.
  • Для взаимодействия нескольких компонентов предпочтителен store.
  • События компонентов дают гибкость при обработке действий пользователя.
  • Для сложных интерфейсов полезно разделять состояние на логические блоки и использовать derived для вычисляемых значений.
  • Избегать прямого изменения вложенных свойств объектов store — лучше создавать новые копии.

Применение этих подходов обеспечивает консистентное, предсказуемое и расширяемое управление состоянием в приложениях на Carbon Components Svelte.