Перейти к основному содержимому

React: Побочные эффекты и хук useEffect

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

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

// выбираем цвет из массива
// и устанавливаем его в качестве фона страницы
function App() {
const [colorIndex, setColorIndex] = React.useState(0);
const colors = ["blue", "green", "red", "orange"];

// работа с DOM API из компонента - это побочный (сторонний) эффект
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
});
// теперь при любом изменении состояния запустится перерендер
// и вызовется эффект-функция

function handleChangeIndex() {
const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
setColorIndex(next);
}

return <button onClick={handleChangeIndex}>Change background color</button>;
}

Чтобы избежать вызова эффект-функции после каждого рендера, можно передать в хук второй аргумент – пустой массив.

function App() {
...
// теперь, сколько бы мы ни кликали, цвет не поменяется
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
}, []);
// эффект-функция отработала только при первом монтировали

return (
<button onClick={handleChangeIndex}>
Change background color
</button>
);
}

Что это за массив? Просто коллекция аргументов, при изменении которых должна отработать наша эффект-функция. Если массив пуст, то она сработает лишь единожды. Если мы положим туда, к примеру, colorIndex, то при каждом его изменении будет меняться фон страницы. Но если перерендер вызван чем-то другим, то коллбэк хука не будет вызван.

function App() {
const [colorIndex, setColorIndex] = React.useState(0);
const colors = ["blue", "green", "red", "orange"];

// говорим react, что вызывать эффект нужно только при изменении colorIndex
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
}, [colorIndex]);

function handleChangeIndex() {
const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
setColorIndex(next);
}

return <button onClick={handleChangeIndex}>Change background color</button>;
}

Хук useEffect также позволяет выполнить какие-то действия при изменении компонента. Здесь можно, например, отписаться от прослушки событий DOM, чтобы не тратить память.

Нужно просто вернуть из эффект-функции другую функцию, и React вызовет ее при перерендере или удалении компонента.

function MouseTracker() {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });

React.useEffect(() => {
// устанавливаем слушатель
window.addEventListener("mousemove", event => {
const { pageX, pageY } = event;
setMousePosition({ x: pageX, y: pageY });
});

// удаляем слушатель при изменении компонента
return () => {
window.removeEventListener("mousemove", event => {
const { pageX, pageY } = event;
setMousePosition({ x: pageX, y: pageY });
});
};
}, []);

return (
<div>
<h1>The current mouse position is:</h1>
<p>
X: {mousePosition.x}, Y: {mousePosition.y}
</p>
</div>
);
}

Получение данных в useEffect

Сам коллбэк не может быть асинхронным (async), поэтому различные асинхронные операции нужно обрабатывать прямо внутри него или вынести в отдельную функцию:

const endpoint = "https://api.github.com/users/codeartistryio";

// пример с промисами
function App() {
const [user, setUser] = React.useState(null);

React.useEffect(() => {
fetch(endpoint)
.then(response => response.json())
.then(data => setUser(data));
}, []);
}

// пример с async/await
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
getUser();
}, []);

// используем отдельную асинхронную функцию
async function getUser() {
const response = await fetch("https://api.github.com/codeartistryio");
const data = await response.json();
setUser(data);
}
}