Для выполнения побочных эффектов, например, прямой работы с DOM в React используется встроенный хук useEffect()
. Он заменяет собой три колбека жизненного цикла:
componentDidMount()
componentDidUpdate()
componentWillUnmount()
Если вы забыли или не знали как они работают, то подробнее об этом можно прочитать в официальной документации.
Начнем с простого примера:
import React, { useState, useEffect } from 'react';
const Example = () => {
const [count, setCount] = useState(0);
// Работает как componentDidMount и componentDidUpdate вместе взятые
// Запускается после рендера компонента
// Вызывается после каждого клика по кнопке
useEffect(() => {
// Состояние доступно внутри за счет обычной области видимости
alert(`Кликов ${count}`);
});
// На классах мы бы сделали так
// Обратите внимание на дублирование
// componentDidMount() {
// alert(`Кликов: ${count}`);
// }
// componentDidUpdate() {
// alert(`Кликов: ${count}`);
// }
return (
<div>
<p>Вы нажали {count} раз(а)</p>
<button onClick={() => setCount(count + 1)}>
Нажми меня
</button>
</div>
);
};
Ниже пример, в котором меняется фон при каждом клике.
See the Pen js_react_hooks_use_ref by Hexlet (@hexlet) on CodePen.
Колбек, переданный в useEffect()
, отрабатывает после первой отрисовки и каждого обновления компонента. То есть произошло объединение методов componentDidUpdate()
и componentDidMount()
. Такое изменение было сделано ради удобства. Мировая практика использования React показала, что, в основном, эффекты происходят после каждого рендера, независимо от того, первая эта отрисовка или все последующие. Как бонус, сократилось количество дублирования и кода. Какие типичные сайд-эффекты встречаются во фронтенде? Например:
Действие хука useEffect()
иногда можно пропускать. Такое бывает полезно либо в целях оптимизации, либо, если эффект имеет смысл только при определенных условиях. Для этого в хук вторым аргументом передается массив значений, которые надо отслеживать между отрисовками. Если хотя бы одно значение из этого массива поменялось, то колбек вызывается, если все значения остались прежними – пропускается.
useEffect(() => {
alert(`Кликов ${count}`);
}, [count]);
// Равносильно
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
alert(`Кликов ${count}`);
}
}
То есть колбек будет вызван только тогда, когда изменится count
. Таким же способом можно передать любой набор переменных, который мы бы хотели связать с изменением эффекта. Если хотя бы одна переменная в переданном массиве поменялась, то эффект сработает, иначе React его пропускает.
Что делать, если нужно запустить useEffect()
только на момент первого рендера (сразу после монтирования)? Для этого достаточно передать пустой массив:
// Заменяет собой componentDidMount
useEffect(() => {
alert(`Кликов ${count}`);
}, []);
Решение не самое очевидное, но технически оно не является особым случаем. К нему нужно просто привыкнуть.
В некоторых случаях эффект нужно сбрасывать. Например, когда эффект после изменения пропсов перестает быть актуальным, его нужно "зачистить". Для этого достаточно вернуть функцию из useEffect()
, внутри которой выполняется очистка:
// Предположим, что этот эффект зависит от пропса userId
useEffect(() => {
const id = setTimeout(/* какой-то код с userId */);
return () => clearTimeout(id);
}, [userId]);
Изменение userId приведет к сбросу текущего таймера и установке нового. Подобный код в классах потребовал бы использования аж 4 колбеков жизненного цикла.
Для имитации componentWillUnmount()
достаточно соединить очистку с пустым массивом вторым параметром:
useEffect(() => {
return () => {
// Эта логика выполнится только при размонтировании компонента
};
}, []);
Первым параметром useEffect()
принимает функцию. Эта функция должна либо ничего не возвращать, либо возвращать функцию для сброса эффекта. Это накладывает некоторое ограничение на использование async await
:
useEffect(async () => {
const data = await axios.get('/todos');
// ...
}, []);
Если использовать async
, то функция уже возвращает промис — это нарушает правило выше. Чтобы этого избежать, можно обернуть асинхронный вызов в функцию и вызвать эту функцию внутри useEffect
:
useEffect(() => {
const requestData = async () => {
const data = await axios.get('/todos');
// ...
};
requestData();
}, []);
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт