React-хуки useEffect и useLayoutEffect: различие и примеры использования

Введение

С появлением хуков в React 16.8 стиль написания функциональных компонентов кардинально изменился. Входящие в число этих хуков useEffect и useLayoutEffect играют особую роль в обработке побочных эффектов в коде. На первый взгляд они могут казаться похожими, однако между ними есть несколько существенных различий. Рассмотрим эти различия, чтобы выяснить, когда в React-проекте стоит использовать useEffect, а когда  —  useLayoutEffect.

Проведем практическое исследование этих двух важнейших хуков React.

Что такое UseEffect?

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

Помните: useEffect  —  это хук, который вам придется использовать в 99% случаев.

Как используется useEffect?

Применяя useEffect, вы отдаете команду React сделать что-то после рендера. React запоминает переданную функцию и вызывает ее позже, после выполнения обновлений DOM.

Синтаксис useEffect:

useEffect(() => {
// Сюда помещается код для обработки побочного эффекта.
}, [dependencies]);

Второй аргумент, [dependencies], представляет собой массив зависимостей. React повторно запустит побочный эффект, только если одна из зависимостей изменилась. Если вы передадите пустой массив ([]), побочный эффект будет запущен один раз после первоначального рендеринга  —  это похоже на componentDidMount в классе компонентов.

Что такое useLayoutEffect?

useLayoutEffect  —  это еще один хук React, который похож на useEffect, но имеет ключевое отличие в сроках выполнения. Он используется для операций, которые должны синхронизироваться с DOM, например для прямого манипулирования DOM или корректировки макета до того, как браузер отрисует его.

Когда используется useLayoutEffect?

useLayoutEffect следует использовать, когда нужно внести изменения в DOM до того, как экран будет обновлен. Это очень важно в сценариях, где необходимо избежать визуальных глюков, таких как мигание изображения, которые могут возникнуть, если проводить манипуляции с DOM после отрисовки DOM на экране.

Синтаксис useLayoutEffect:

useLayoutEffect(() => {
// Код, который взаимодействует с DOM.
}, [dependencies]);

Как и useEffect, useLayoutEffect также принимает массив зависимостей в качестве второго аргумента.

Сравнение useEffect и useLayoutEffect в действии

После рассмотрения useEffect и useLayoutEffect по отдельности стоит изучить их в действии в рамках одного компонента. Такое сравнение даст более четкое понимание порядка выполнения и поведения каждого из этих хуков.

Создадим один функциональный компонент, который включает и useEffect, и useLayoutEffect. Добавим журналы консоли к каждому хуку, чтобы наблюдать за порядком их выполнения.

import React, { useEffect, useLayoutEffect } from 'react';

const Home = () => {

useLayoutEffect(() => {
console.log('useLayoutEffect - Runs first, but after DOM mutations');
}, []);

useEffect(() => {
console.log('useEffect - Runs second, after the browser has painted');
}, []);

return (
<div>Hello, React Hooks!</div>
);
};

export default Home;

В этом компоненте и useLayoutEffect, и useEffect имеют пустые массивы зависимостей. Таким образом, они должны запускаться после начального рендеринга.

useEffect и useLayoutEffect
  1. useLayoutEffect. Этот хук запускается первым. Он запускается после завершения всех мутаций DOM, но до того, как браузер успел провести рендеринг. Поэтому он является идеальным выбором для любых манипуляций с DOM или вычислений, которые должны произойти сразу после обновления DOM, но до того, как пользователь увидит что-либо. Журнал консоли внутри useLayoutEffect появится первым.
  2. useEffect. Этот хук запускается после useLayoutEffect. Он срабатывает после завершения цикла рендеринга компонента и обновления экрана. Такое поведение означает, что useEffect лучше всего подходит для задач, которые не требуют немедленного синхронного обновления DOM, например для вызовов API или настройки подписок. Журнал консоли внутри useEffect появится после журнала от useLayoutEffect.

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

Практические примеры: useEffect и useLayoutEffect в действии

Чтобы лучше понять нюансы использования useEffect и useLayoutEffect, рассмотрим несколько практических примеров. Они проиллюстрируют, как эффективно использовать каждый хук в различных ситуациях.

Стоит отметить, что во многих случаях, особенно в простых сценариях или в современных высокопроизводительных браузерах, эти различия могут быть визуально не заметны.

Использование UseEffect для сбора данных

Получение данных  —  распространенный вариант использования useEffect. Он позволяет запрашивать и загружать данные из API во время рендеринга компонента.

Вот полный пример компонента React, который использует useEffect для получения данных из API JSONPlaceholder  —  бесплатной имитации онлайн REST API:

import React, { useState, useEffect } from 'react';

const App = () => {
const [data, setData] = useState([]);

useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const jsonData = await response.json();
setData(jsonData);
} catch (error) {
console.error('Error fetching data: ', error);
}
};
fetchData();
}, []);
return (
<div>
<h1>Data Fetched from JSONPlaceholder API</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
};

export default App;

Компонент отображает список названий, полученных из API. Пустой массив в списке зависимостей useEffect гарантирует одноразовую выборку данных, аналогично componentDidMount в компонентах класса.

Использование useLayoutEffect для манипуляций с DOM

Рассмотрим ситуацию, когда необходимо изменить позицию прокрутки списка к верхней части сразу после изменения состояния. Использование useEffect может привести к заметному миганию изображения, так как корректировка происходит после обновления экрана. useLayoutEffect решает эту проблему, обеспечивая выполнение корректировки до рендеринга экрана.

Вот пример:

import React, { useState, useLayoutEffect, useRef } from 'react';

const App = () => {
const [items, setItems] = useState([]);
const listRef = useRef(null);
const addItems = () => {
const newItems = [...Array(5).keys()].map(i => `Item ${i + items.length}`);
setItems([...items, ...newItems]);
};
useLayoutEffect(() => {
// Настройка положения прокрутки перед обновлением экрана
if (listRef.current) {
listRef.current.scrollTop = 0;
}
}, [items]); // Зависимость от состояния 'items'
return (
<div>
<button onClick={addItems}>Add Items</button>
<ul ref={listRef}>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
export default App;

Вот что происходит в этом компоненте.

  • Хук useState используется для создания состояния items, в котором хранится список элементов.
  • Новые элементы добавляются в список с помощью кнопки.
  • Хук useLayoutEffect используется для прокрутки списка вверх каждый раз, когда добавляются новые элементы (это делается до того, как браузер отрисует обновленный пользовательский интерфейс, что предотвращает мигание или скачки в позиции прокрутки).
  • Атрибут ref (listRef) используется для ссылки на DOM-элемент списка для манипулирования его позицией прокрутки.

Этот пример демонстрирует, как можно использовать useLayoutEffect для плавной, без мигания, корректировки DOM в ответ на изменения состояния компонента React.

Заключение

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

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

С другой стороны, useLayoutEffect незаменим, когда нужно сделать синхронные обновления в прямой реакции на изменения DOM. Это идеальный инструмент для предотвращения визуальных глюков и обеспечения плавного взаимодействия с пользователем при работе с DOM.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Jayanth babu S: From My Experience: useEffect vs useLayoutEffect in React

Предыдущая статьяЭти декораторы Python позволят сократить код вдвое
Следующая статьяБайт-код JVM: манипулирование и инструментация