Залиште useEffect у спокої: Краще використання React-хуків

React

У світі React хук useEffect є одним із найважливіших інструментів для роботи з побічними ефектами, такими як завантаження даних, інтеграція з API або обробка підписок. Проте зловживання useEffect може призвести до труднощів у відлагодженні, зайвих рендерів та навіть до порушення логіки роботи додатку. У цій статті ми розглянемо основні проблеми використання useEffect, шляхи їх вирішення та як забезпечити чистий і оптимізований код.

Чому useEffect часто використовують неправильно?

useEffect дозволяє виконувати побічні ефекти у функціональних компонентах, але часто використовується не за призначенням. Деякі розробники застосовують цей хук для обчислень, оновлення стану чи навіть для синхронізації, що порушує принципи React.

Основні проблеми:

  1. Непотрібні побічні ефекти: Використання useEffect для логіки, яка може бути реалізована без цього.
  2. Залежність від некоректного масиву залежностей: Пропущені або зайві залежності призводять до непередбачуваної поведінки.
  3. Нестабільність функцій та об’єктів: Кожен новий рендер створює нові посилання, що викликає зайві виклики useEffect.

Поширені помилки використання useEffect

1. Використання для синхронізації стану

Помилка:
Розробники часто використовують useEffect для синхронізації стану, що створює зайві залежності та може призвести до нескінченних циклів.

const [count, setCount] = useState(0);
const [double, setDouble] = useState(0);

useEffect(() => {
  setDouble(count * 2);
}, [count]);

Як виправити:
Стан double є похідним від count, тому його можна обчислювати безпосередньо в рендері.

const [count, setCount] = useState(0);
const double = count * 2;

2. Завантаження даних без відміни запиту

Помилка:
Під час асинхронних операцій (наприклад, запитів до API) useEffect не враховує ситуацій, коли компонент може бути демонтований до завершення запиту.

useEffect(() => {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => setData(data));
}, []);

Як виправити:
Додавайте механізм скасування запитів:

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

  fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      if (isMounted) {
        setData(data);
      }
    });

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

3. Виклик функції всередині useEffect без мемоізації

Помилка:
Функції або об’єкти, які використовуються в useEffect, можуть створювати зайві виклики, якщо їх посилання змінюються на кожному рендері.

const fetchData = () => {
  console.log('Fetching data...');
};

useEffect(() => {
  fetchData();
}, [fetchData]); // Викликається зайве через зміну посилання

Як виправити:
Використовуйте useCallback для мемоізації функції:

const fetchData = useCallback(() => {
  console.log('Fetching data...');
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]);

Коли не потрібен useEffect

1. Для обчислень

Обчислення, які залежать від стану чи пропсів, краще виконувати безпосередньо у функціональному компоненті.

Поганий приклад:

const [count, setCount] = useState(0);
const [triple, setTriple] = useState(0);

useEffect(() => {
  setTriple(count * 3);
}, [count]);

Кращий підхід:

const [count, setCount] = useState(0);
const triple = count * 3;

2. Для доступу до DOM

Для простого доступу до DOM використовуйте ref, а не useEffect.

Поганий приклад:

useEffect(() => {
  document.title = `Ви натиснули ${count} разів`;
}, [count]);

Кращий підхід:
У цьому випадку useEffect виправданий, але краще звести доступ до DOM до мінімуму.

Кращі практики використання useEffect

  1. Оцінюйте необхідність:
    Перед тим як додати useEffect, запитайте себе, чи можна виконати цю логіку без нього.
  2. Залежності:
    Уважно стежте за масивом залежностей. Використовуйте ESLint для автоматичної перевірки.
  3. Очищення:
    Завжди очищуйте побічні ефекти (підписки, таймери) у функції return з useEffect.
useEffect(() => {
  const interval = setInterval(() => {
    console.log('Таймер працює');
  }, 1000);

  return () => clearInterval(interval); // Очищення
}, []);
  1. Мемоізація:
    Використовуйте useMemo і useCallback, щоб уникнути зайвих викликів.

Альтернативи useEffect

1. React Query

Для роботи з API використовуйте спеціалізовані бібліотеки, як-от React Query, які беруть на себе всі аспекти управління станом, кешуванням і синхронізацією.

import { useQuery } from 'react-query';

const { data, error } = useQuery('fetchData', () =>
  fetch('/api/data').then(res => res.json())
);

2. Контекст або кастомні хуки

Інколи useEffect можна замінити кастомними хуками, які краще структурують логіку.

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

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

  return data;
}

const data = useFetchData('/api/data');

Висновок

useEffect — це потужний інструмент, але його потрібно використовувати обережно. Багато випадків, у яких застосовується useEffect, можуть бути реалізовані простішими способами, такими як обчислення у функціональному компоненті, мемоізація функцій чи використання кастомних хуків. Головна мета — уникнути зловживання та забезпечити, щоб ваш код був чистим, зрозумілим і продуктивним.

Дотримуючись цих порад, ви зможете уникнути поширених проблем і створювати ефективніші React-додатки!