Отмена запросов и cleanup

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

Использование useEffect для управления побочными эффектами

В Preact хук useEffect выполняет ту же роль, что и в React: позволяет выполнять побочные эффекты после рендера компонента. Важной особенностью является возможность возвращать функцию очистки (cleanup function), которая вызывается при размонтировании компонента или перед следующим вызовом эффекта.

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

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

  useEffect(() => {
    const controller = new AbortController();

    fetch('https://api.example.com/data', { signal: controller.signal })
      .then(response => response.json())
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      });

    // Функция очистки вызывается при размонтировании компонента
    return () => controller.abort();
  }, []);

  return (
    <div>{data ? JSON.stringify(data) : 'Загрузка...'}</div>
  );
}

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

  • AbortController используется для отмены fetch-запросов.
  • Функция, возвращаемая из useEffect, автоматически вызывается при размонтировании компонента.
  • Проверка err.name !== 'AbortError' предотвращает обработку ошибки отмененного запроса как обычной ошибки.

Отмена асинхронных операций

Асинхронные операции внутри компонентов могут быть не только fetch-запросами. Это таймеры, WebSocket-подключения, анимации или подписки на события. Для предотвращения утечек памяти и некорректного обновления состояния следует всегда использовать cleanup.

Пример с таймером:

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

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

  useEffect(() => {
    const interval = setInterval(() => setCount(c => c + 1), 1000);

    return () => clearInterval(interval);
  }, []);

  return <div>Секунд прошло: {count}</div>;
}

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

  • setInterval создаёт повторяющийся таймер, который необходимо очищать при размонтировании.
  • clearInterval предотвращает продолжение работы таймера после удаления компонента.

Отмена подписок и сторонних ресурсов

При работе с библиотеками, которые создают подписки на события или WebSocket-соединения, необходимо также использовать cleanup:

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

function WebSocketComponent() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket('wss://example.com/socket');

    socket.addEventListener('message', event => {
      setMessages(prev => [...prev, event.data]);
    });

    return () => {
      socket.close();
    };
  }, []);

  return (
    <ul>
      {messages.map((msg, index) => <li key={index}>{msg}</li>)}
    </ul>
  );
}

Важные моменты:

  • WebSocket соединение закрывается в cleanup-функции через socket.close().
  • Предотвращается накопление подписчиков и повторное получение сообщений для уже несуществующих компонентов.

Практика безопасного обновления состояния

Даже при отмене запросов или таймеров важно убедиться, что состояние компонента обновляется только когда компонент всё ещё монтирован. Использование флагов isMounted часто помогает:

useEffect(() => {
  let isMounted = true;

  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(result => {
      if (isMounted) setData(result);
    });

  return () => { isMounted = false; };
}, []);

Этот подход предотвращает “setState on unmounted component” ошибки, которые часто возникают при асинхронных операциях.

Рекомендации по cleanup

  • Всегда возвращать функцию очистки из useEffect для асинхронных операций.
  • Использовать AbortController для fetch-запросов.
  • Чистить таймеры и подписки на события.
  • Обновлять состояние только для смонтированных компонентов.
  • Проверять сторонние библиотеки на наличие встроенного метода cleanup и вызывать его при размонтировании.

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