use новый хук в React 19: практические приемы
Хук use объединяет состояние и побочный эффект в React 19, сокращая код и устраняя лишние ререндеры. Узнайте, как правильно его использовать, избежать бесконечных обновлений и писать тесты.
Если в проекте на React 18 вы привыкли писать useState
+ useEffect
парами, после перехода на React 19 ваш код может выглядеть раздутым. Новый инструмент use
объединяет состояние и эффект в одной декларации, избавляя от лишних синхронизаций.
В этой статье разберем, как заставить use
работать без сюрпризов, какие подводные камни есть у синхронных и асинхронных вызовов, и какие приемы помогут держать рендер-цикл под контролем.
Что дает хук use
: объединение состояния и эффекта
use
объявляет “состояние-привязанное действие” в одном месте. Вместо:
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then((r) => r.json())
.then(setData);
}, [url]);
получаем компактный вариант:
const data = use(url, async (u) => {
const r = await fetch(u);
return r.json();
});
use
принимает два аргумента: ключ (зависимость) и функцию-обработчик.- Возвращаемое значение уже находится в состоянии, его достаточно лишь читать.
Сокращенный синтаксис сразу уменьшает количество строк и устраняет двойной setState
-цикл. Для привычных к useEffect
разработчиков use
выглядит как “один-в-один” паттерн с более явным потоком данных.
Синтаксис и типы параметров
Параметр | Описание | Пример |
---|---|---|
key | Значение, от которого зависит переиспользование. Может быть примитивом или массивом. | userId const userId = props.id; |
fn | Функция, вызываемая при изменении key . Возвращает промис или синхронное значение. | async id => { const resp = await fetchUser(id); return resp.data; } |
options (необязательно) | Объект с флагамиlazy , debounce , resetOnUnmount . | { lazy: true, debounce: 300 } |
Типы проверяются TypeScript-ом. Если fn
возвращает Promise<T>
, переменная будет иметь тип T | undefined
до завершения. Это помогает избежать ошибок “null-reference”.
Как избежать бесконечного цикла обновлений
Самая частая ошибка использовать внутри fn
переменную, меняющуюся каждый рендер. Например:
const [filter, setFilter] = useState("");
const result = use(filter, () => heavyCalc(filter, Math.random()));
Math.random()
генерирует новое значение при каждом рендере, и use
воспринимает его как новую зависимость. Это приводит к бесконечным вызовам.
Решение: вынести “нестабильные” значения в options
или мемоизировать их.
const stableSeed = useMemo(() => Math.random(), []);
const result = use(filter, () => heavyCalc(filter, stableSeed));
Теперь stableSeed
фиксируется один раз, а use
перезапускается только при изменении filter
. Такой подход избавляет от лишних перезапусков и делает производительность предсказуемой.
Работа с асинхронными данными
use
автоматически управляет статусом загрузки. Расширенный вариант useAsync
возвращает data
, error
и loading
.
const { data, loading, error } = useAsync(userId, async (id) => {
const res = await fetch(`/api/user/${id}`);
if (!res.ok) throw new Error("Network error");
return res.json();
});
if (loading) return <Spinner />;
if (error) return <ErrorMessage err={error} />;
return <UserCard user={data} />;
use
отменяет запрос при размонтировании, предотвращая ошибки “setState on unmounted component”. Подробнее о отмене запросов читайте в статье о виртуализация списка.
Тестирование и отладка use
Тестировать хук проще, чем два отдельных (useState
+ useEffect
). Библиотека @testing-library/react-hooks
(или ее современный аналог) позволяет написать:
import { renderHook, act } from "@testing-library/react-hooks";
import { use } from "react";
test("fetches data on key change", async () => {
const mockFetch = jest.fn(async (id) => ({ id, name: "User" + id }));
const { result, waitForNextUpdate } = renderHook(() => use(1, mockFetch));
expect(result.current).toBeUndefined();
await waitForNextUpdate();
expect(result.current).toEqual({ id: 1, name: "User1" });
});
В случае ошибок use
бросает исключения, которые легко перехватить в тесте. Для профайлинга включите React.StrictMode
и просмотрите сообщения в консоли. Если возникают неожиданные перерендеры, обратитесь к рекомендациям по оптимизации рендера.
FAQ / чеклист
- Не включайте в
fn
переменные, изменяющиеся каждый рендер. Меморизируйте их черезuseMemo
. - Используйте
lazy
-опцию, если запрос дорогой и не нужен сразу. Это откладывает загрузку до момента реального обращения. - Обрабатывайте ошибки внутри
fn
. Исключения пробрасываются в компонент. - Отменяйте запросы при размонтировании.
use
делает это автоматически, но вашfetch
должен поддерживатьAbortController
. - Не забывайте про типизацию. Явно указывайте тип возвращаемого значения функции, иначе получите
any
.
Новый хук use
удобен, когда нужен быстрый способ связать состояние с побочным эффектом без лишнего boilerplate.
Он особенно полезен в формах, загрузке данных и небольших UI-виджетах, где каждый ререндер стоит дорого. Применяйте предложенные практики и ваш код станет чище, а приложение быстрее.
Больше информации о React 19 найдете в статье React 19: что нового.