use это API React 19, который читает значение из промиса или контекста во время рендера компонента. use(promise) приостанавливает компонент, пока промис не выполнится, а тем временем показывает ближайший запасной вариант <Suspense>; use(context) работает как useContext, но может стоять внутри if или цикла.
Функция fetchUser ниже заменяет настоящий запрос.
Первый рендер на секунду показывает «Loading...», затем профиль. Нажмите User 2: запасной вариант возвращается, пока новый промис ожидает. У Profile нет собственного состояния загрузки; он написан так, будто данные уже есть.
Как работает use(promise)
Когда Profile вызывает use(userPromise):
- Если промис выполнен,
useвозвращает его значение, и рендер продолжается. - Если он ещё ожидает, React прекращает рендер
Profileи показывает запасной вариант ближайшего<Suspense>выше. Когда промис завершится, React снова рендеритProfile, и на этот разuseвозвращает значение. - Если он отклонён, React вместо этого показывает ближайшую границу ошибок.
React запоминает результат в самом объекте промиса. Поэтому при следующем рендере промис должен быть тем же объектом.
Чтобы старый профиль оставался на экране, пока загружается следующий, устанавливайте промис внутри перехода. Тогда React ждёт, а не показывает запасной вариант снова:
<button onClick={() => startTransition(() => setUserPromise(fetchUser(2)))}>User 2</button>
Где создавать промис
На этом спотыкаются чаще всего. Вызов fetch внутри компонента, который его читает, выглядит естественно и не работает:
// Do not do this
function Profile({ id }) {
const user = use(fetchUser(id)); // a new promise on every render
return <p>{user.name}</p>;
}
Каждый рендер вызывает fetchUser и получает новый ожидающий промис. Profile приостанавливается. Когда этот промис выполняется, React снова рендерит Profile, который создаёт ещё один ожидающий промис, который снова приостанавливает компонент. Компонент так и не показывает данные, а вкладка сети заполняется запросами.
Создавайте промис там, где он не пересоздаётся при каждом рендере читающего компонента:
- В родителе: в состоянии (как в первом примере) или в обработчике события.
- На уровне модуля, когда данные не зависят от пропсов:
const configPromise = fetchConfig();над компонентом. - В кэше по ключу аргументов, чтобы для одного и того же id возвращался один и тот же промис.
- Во фреймворке: серверный компонент может передать промис клиентскому компоненту как пропс, и загрузчики маршрутов делают то же. Именно для этого случая
useи создавался.
Вот версия с кэшем, где компонент может запрашивать данные по id и всё равно получать стабильный промис:
Пройдитесь по трём городам, а затем вернитесь к уже открытому. Консоль выводит по одному запросу на город, а город, который вы уже видели, появляется без запасного варианта, потому что его промис уже выполнен. Удалите проверку if (!cache.has(city)), и компонент снова будет приостанавливаться бесконечно.
Настоящему кэшу также нужен способ устаревания записей. Это берут на себя библиотеки данных и фреймворки, поэтому большинство приложений получают промисы от них.
use(context), даже внутри if
use(SomeContext) возвращает то же значение, что и useContext(SomeContext). Разница в том, где его можно вызывать. Все остальные хуки должны выполняться на верхнем уровне, в одном и том же порядке при каждом рендере (правила хуков). use можно вызывать после раннего return, внутри условия или в цикле.
Нажмите кнопку, чтобы переключаться между двумя ветками. Замените use(UserContext) на useContext(UserContext), и код нарушит правила хуков, потому что хук выполнялся бы в одних рендерах и не выполнялся бы в других.
У use всё равно есть ограничения: вызывайте его только во время рендера компонента или внутри пользовательского хука. В обработчике события или эффекте читайте контекст через значение, которое вы уже получили во время рендера.
Ошибки: границы ошибок, а не try/catch
Когда промис отклоняется, ошибка уходит в ближайшую границу ошибок, так же как ошибка рендера.
Откройте Post 2, и граница покажет сообщение об ошибке. Снова откройте Post 1, и всё работает: key={id} даёт границе свежее состояние для каждого поста, поэтому она не продолжает показывать старую ошибку.
Перехватить ожидающий или отклонённый use через try/catch нельзя. Пока промис ожидает, use прерывает рендер, выбрасывая особое значение, и catch вокруг него проглотил бы его. Перехваченное значение это ошибка, сообщение которой начинается с "Suspense Exception: This is not a real error!", а в разработке React также выводит "use was called from inside a try/catch block". Чтобы показать запасное значение вместо экрана ошибки, обработайте отклонение на промисе, прежде чем передавать его вниз:
const postPromise = fetchPost(id).catch(() => 'This post could not be loaded.');
use в сравнении с загрузкой в useEffect
Классический клиентский шаблон загружает данные в эффекте и хранит три значения состояния:
function Profile({ id }) {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
fetchUser(id).then(
(u) => !ignore && setUser(u),
(e) => !ignore && setError(e),
);
return () => {
ignore = true;
};
}, [id]);
if (error) return <p>Error</p>;
if (!user) return <p>Loading...</p>;
return <p>{user.name}</p>;
}
useEffect | use | |
|---|---|---|
| Когда начинается запрос | после того как первый рендер оказался на экране | когда создаётся промис, возможно до рендера компонента |
| Состояние загрузки | ваш собственный if (!user) | ближайший <Suspense> |
| Ошибки | ваше собственное состояние error | ближайшая граница ошибок |
| Состояния гонки | защищаете вы сами (ignore) | компонент всегда читает промис, который ему дали |
| Кто создаёт промис | компонент | родитель, кэш или фреймворк |
Версия с эффектом самодостаточна, поэтому она по-прежнему самый распространённый подход в приложениях без фреймворка; страница о загрузке данных разбирает её полностью. use раскрывается, когда что-то выше компонента рано запускает запрос и передаёт промис вниз.
Часто задаваемые вопросы
Что делает хук use в React?
use(resource) возвращает значение промиса или контекста. С промисом компонент приостанавливается, пока тот не выполнится, а ближайший <Suspense> показывает свой запасной вариант; если промис отклоняется, вместо этого показывается ближайшая граница ошибок.
Можно ли вызывать use по условию?
Да. use это единственный хук, который можно вызывать внутри инструкций if, циклов и после раннего return. Его всё равно нужно вызывать из компонента или хука, а не из обработчика события или эффекта.
Почему use(fetch()) внутри компонента зацикливается?
Каждый рендер создаёт новый промис. Компонент приостанавливается на нём, React рендерит снова, когда тот выполнится, и этот рендер создаёт ещё один ожидающий промис. Создавайте промис вне рендера: в состоянии родителя, на уровне модуля, в кэше или в загрузчике фреймворка.
Можно ли обернуть use в try/catch?
Нет. Когда промис ожидает, use прерывает рендер, выбрасывая значение, и перехват его ломает. Отклонённый промис обрабатывайте границей ошибок или добавьте к промису .catch(), чтобы он разрешался в запасное значение.
Заменить загрузку данных в useEffect на use?
use нужен промис, который создаётся один раз и переиспользуется, и именно такие дают фреймворки и библиотеки данных. В обычном клиентском приложении загрузка в эффекте (или через библиотеку вроде TanStack Query) по-прежнему распространена и нормальна.