Теория состояния и управление данными

Состояние (state) является ключевым элементом в архитектуре компонентов Preact. Оно представляет собой объект, который хранит данные, определяющие текущее поведение и отображение компонента. В Preact состояние обычно создается с помощью хука useState:

import { h } from 'preact';
import { useState } from 'preact/hooks';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Счётчик: {count}</p>
      <button onCl ick={() => setCount(count + 1)}>Увеличить</button>
    </div>
  );
}

Ключевые моменты использования состояния:

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

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

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

import { h } from 'preact';
import { useReducer } from 'preact/hooks';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Счётчик: {state.count}</p>
      <button onCl ick={() => dispatch({ type: 'increment' })}>Увеличить</button>
      <button onCl ick={() => dispatch({ type: 'decrement' })}>Уменьшить</button>
    </div>
  );
}

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

  • Централизованная логика изменения состояния.
  • Удобно для управления сложными объектами или массивами.
  • Повышает читаемость и тестируемость компонентов.

Локальное и глобальное состояние

В Preact локальное состояние удобно для отдельных компонентов. Для обмена данными между компонентами используют контекст (createContext). Контекст позволяет передавать данные глубоко в дерево компонентов без явного проброса через props.

import { h } from 'preact';
import { createContext } from 'preact';
import { useContext, useState } from 'preact/hooks';

const ThemeContext = createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <button
      style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}
      onCl ick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
    >
      Переключить тему
    </button>
  );
}

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

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

Связь состояния и эффектов

Для управления побочными эффектами (side effects) применяется хук useEffect, который синхронизирует состояние с внешними действиями: запросами к API, подписками, таймерами.

import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(setData);
  }, []); // пустой массив зависимостей — вызов один раз при монтировании

  if (!data) return <p>Загрузка...</p>;
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

Принципы работы useEffect:

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

Ререндеры и оптимизация

Перерисовка компонентов происходит при изменении состояния или props. Для повышения производительности:

  • Использовать useMemo для кэширования вычислений, зависящих от состояния.
  • Применять useCallback для сохранения функций, чтобы не вызывать лишние перерендеры дочерних компонентов.
  • Минимизировать количество данных в состоянии, хранить только необходимое для рендера.
import { h } from 'preact';
import { useState, useMemo } from 'preact/hooks';

function ExpensiveCalculation({ num }) {
  const result = useMemo(() => {
    console.log('Вычисление...');
    return num * 2;
  }, [num]);

  return <p>Результат: {result}</p>;
}

Использование этих подходов позволяет строить компоненты Preact эффективными, с управляемым состоянием и минимальной нагрузкой на рендеринг, сохраняя при этом чистоту и предсказуемость архитектуры.