Хук useEffect позволяет выполнять побочные эффекты в ваших компонентах.
Некоторые примеры побочных эффектов:
- Получение данных
- Прямая модификация DOM
- Таймеры
useEffect принимает два аргумента. Второй аргумент необязателен.
useEffect(<функция>, <зависимости>)
Давай посмотрим на пример с таймером.
Пример
Используя setTimeout(), увеличим счётчик на единицу спустя одну секунду после первого рендера:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount(count => count + 1);
}, 1000);
});
return <h1>Отрендерился {count} раз!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);
Но подождите!!! Счёт продолжается бесконечно, хотя должен был сработать только один раз!
Дело в том, что useEffect срабатывает при каждом рендере. То есть, когда изменяется значение счётчика, происходит повторный рендер, который запускает эффект снова.
Нам это не подходит. Есть несколько способов контролировать выполнение побочных эффектов.
Всегда включайте второй параметр — массив зависимостей. В этот массив можно дополнительно передать зависимости для useEffect.
Примеры
1. Нет зависимостей:
useEffect(() => {
//Выполняется при каждом рендере
});
2. Пустой массив:
useEffect(() => {
//Выполняется только при первом рендере
}, []);
3. Пропсы или значения состояния:
useEffect(() => {
//Выполняется при первом рендере
//И всякий раз, когда меняется любая зависимость
}, [prop, state]);
Итак, исправим ошибку и заставим эффект срабатывать только при начальном рендере.
Пример
Запустим эффект только при начальном рендере:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
setCount(count => count + 1);
}, 1000);
}, []); //<- добавляем пустой массив здесь
return <h1>Отрендерился {count} раз!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);
Пример
Вот пример хука useEffect, зависящего от переменной. Если обновляется переменная count, эффект запустится вновь:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);
useEffect(() => {
setCalculation(() => count * 2);
}, [count]); //<- добавляем переменную count здесь
return (
<>
<p>Счётчик: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
<p>Расчёт: {calculation}</p>
</>
);
}
createRoot(document.getElementById('root')).render(
<Counter />
);
Если имеется несколько зависимостей, их следует перечислить в массиве зависимостей useEffect.
Очистка эффекта
Некоторым эффектам требуется очистка для предотвращения утечек памяти.
Таймеры, подписки, слушатели событий и прочие эффекты, которые становятся ненужными, должны быть очищены.
Делается это добавлением функции возврата в конце хука useEffect.
Пример
Очищаем таймер в конце хука useEffect:
import { useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setTimeout(() => {
setCount(count => count + 1);
}, 1000);
return () => clearTimeout(timer);
}, []);
return <h1>Отрендерился {count} раз!</h1>;
}
createRoot(document.getElementById('root')).render(
<Timer />
);