Эквиваленты в функциональных компонентах через хуки

Функциональные компоненты в Preact представляют собой обычные JavaScript-функции, которые возвращают разметку JSX. В отличие от классовых компонентов, они не имеют собственного состояния и методов жизненного цикла по умолчанию. Основная задача функционального компонента — принимать props и возвращать JSX.

import { h } from 'preact';

function Button({ label, onClick }) {
  return <button onCl ick={onClick}>{label}</button>;
}

Ключевым моментом является то, что функциональные компоненты более лёгкие и предсказуемые, поскольку не требуют this и не создают экземпляров классов.


Использование useState для состояния

Хук 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 может принимать начальное значение любого типа: число, строку, объект, массив.
  • Обновление состояния через функцию (setCount(prev => prev + 1)) гарантирует корректность при асинхронных изменениях.
  • Каждый вызов useState создаёт отдельную независимую ячейку состояния.

Управление побочными эффектами через useEffect

Хук useEffect позволяет выполнять действия после рендера компонента: запросы к API, подписки, таймеры, модификацию DOM.

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval); // очистка при размонтировании
  }, []); // пустой массив зависимостей — эффект выполнится один раз

  return <p>Прошло секунд: {seconds}</p>;
}

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

  • Второй аргумент useEffect — массив зависимостей. Эффект выполняется при изменении любой из зависимостей.
  • Возвращаемая функция используется для очистки ресурсов, предотвращая утечки памяти.
  • useEffect заменяет большинство методов жизненного цикла классовых компонентов (componentDidMount, componentDidUpdate, componentWillUnmount).

useRef для доступа к DOM и сохранения значений

Хук useRef создаёт объект, который сохраняет своё значение между рендерами без триггера повторного рендера.

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

function FocusInput() {
  const inputRef = useRef();

  const focus = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onCl ick={focus}>Сфокусировать</button>
    </div>
  );
}

Применение useRef:

  • Доступ к DOM-элементам (inputRef.current).
  • Сохранение любого изменяемого значения между рендерами без повторного рендера.
  • Хранение идентификаторов таймеров, состояния подписок и других ресурсов.

useMemo и useCallback для оптимизации

useMemo мемоизирует вычисленные значения, предотвращая их повторное вычисление при каждом рендере.

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

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

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

useCallback мемоизирует функции, чтобы их ссылка оставалась неизменной между рендерами.

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

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

  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return <button onCl ick={increment}>Счёт: {count}</button>;
}

Рекомендации по применению:

  • Использовать useMemo и useCallback при дорогих вычислениях или передаче функций в дочерние компоненты, чтобы избежать ненужных ререндеров.
  • Не злоупотреблять — мемоизация имеет накладные расходы и может быть избыточной для простых случаев.

Создание пользовательских хуков

Пользовательские хуки позволяют инкапсулировать повторяющуюся логику и переиспользовать её в разных компонентах.

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

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(json => {
        setData(json);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}

Использование:

function UsersList() {
  const { data, loading } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return <p>Загрузка...</p>;
  return (
    <ul>
      {data.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

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

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

Работа с контекстом через useContext

Контекст позволяет передавать данные через дерево компонентов без явного пробрасывания пропсов.

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

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Кнопка</button>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );
}

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

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

Итоговый паттерн использования хуков

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

  1. Импорт хуков.
  2. Определение состояния через useState.
  3. Выполнение побочных эффектов через useEffect.
  4. Доступ к DOM через useRef.
  5. Оптимизация вычислений через useMemo и useCallback.
  6. Использование контекста через useContext.
  7. При необходимости вызов пользовательских хуков для переиспользуемой логики.

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