useTransition
useTransition — это React-хук, позволяющий обновлять состояние без блокировки пользовательского интерфейса.
const [isPending, startTransition] = useTransition()
- Справочник
-
Использование
- Отмечивание обновления состояния как неблокирующей транзиции
- Обновление родительского компонента в транзиции
- Отображение ожидающего визуального состояния во время транзиции
- Предотвращение нежелательных индикаторов загрузки
- Создание маршрутизатора с поддержкой Suspense
- Отображение ошибки пользователю с помощью границы ошибок
- Отладка
Справочник
useTransition()
Вызовите useTransition в главном уровне вашего компонента, чтобы отметить некоторые обновления состояния как транзиции.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
} Параметры
useTransition не принимает никаких параметров.
Возвращаемое значение
useTransition возвращает массив ровно из двух элементов:
- Флаг
isPending, указывающий, есть ли ожидающая транзиция. - Функция
startTransition, позволяющая отметить обновление состояния как транзицию.
startTransition функция
Функция startTransition возвращаемая useTransition позволяет вам отметить обновление состояния как транзицию.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
} Параметры
-
scope: Функция, которая обновляет состояние, вызывая одну или несколько функцийset. React немедленно вызываетscopeбез параметров и отмечает все обновления состояния, запланированные синхронно во время вызова функцииscope, как транзиции. Они будут неблокирующими и не отображать нежелательные индикаторы загрузки.
Возвращаемое значение
startTransition ничего не возвращает.
Примечания
-
useTransition— это хук, поэтому его можно вызывать только внутри компонентов или пользовательских хуков. Если вам нужно запустить транзицию где-то ещё (например, из библиотеки данных), вызовите автономную функциюstartTransition. -
Вы можете обернуть обновление в транзицию только если у вас есть доступ к функции
setэтого состояния. Если вы хотите запустить транзицию в ответ на какой-либо проп или значение пользовательского хука, попробуйте использоватьuseDeferredValue. -
Функция, которую вы передаёте в
startTransition, должна быть синхронной. React немедленно выполняет эту функцию, отмечая все обновления состояния, которые происходят во время её выполнения, как транзиции. Если вы попытаетесь выполнить больше обновлений состояния позже (например, в таймауте), они не будут отмечены как транзиции. -
Функция
startTransitionимеет стабильный идентификатор, поэтому её часто опускают из зависимостей эффекта, но включение её не заставит эффект сработать. Если linter позволяет вам опустить зависимость без ошибок, это безопасно. Узнайте больше об удалении зависимостей эффекта. -
Обновление состояния, отмеченное как транзиция, может быть прервано другими обновлениями состояния. Например, если вы обновляете компонент диаграммы внутри транзиции, но затем начинаете печатать в поле ввода, пока диаграмма находится в середине повторной отрисовки, React перезапустит работу по рендерингу компонента диаграммы после обработки обновления ввода.
-
Обновления транзиции нельзя использовать для управления текстовыми полями.
-
Если имеется несколько текущих транзиций, React в настоящее время объединяет их вместе. Это ограничение, которое, вероятно, будет удалено в будущей версии.
Использование
Отмечивание обновления состояния как неблокирующей транзиции
Вызовите useTransition в главном уровне вашего компонента, чтобы отметить обновления состояния как неблокирующие транзиции.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
} useTransition возвращает массив ровно из двух элементов:
- Флаг
isPending, указывающий, есть ли ожидающая транзиция. - Функция
startTransition, позволяющая отметить обновление состояния как транзицию.
Затем вы можете отметить обновление состояния как транзицию следующим образом:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
} Транзиции позволяют поддерживать отзывчивость пользовательского интерфейса даже на медленных устройствах.
С транзицией ваш пользовательский интерфейс остаётся отзывчивым во время повторной отрисовки. Например, если пользователь нажимает вкладку, а затем меняет своё решение и нажимает другую вкладку, он может сделать это, не дожидаясь завершения первой повторной отрисовки.
Разница между useTransition и обычными обновлениями состояния
Пример 1 из 2:
Обновление текущей вкладки в транзиции
В этом примере вкладка «Статьи» искусственно замедлена, так что её рендеринг занимает не менее секунды.
Нажмите на «Статьи», а затем немедленно на «Контакты». Обратите внимание, что это прерывает медленный рендеринг «Статей». Вкладка «Контакты» отображается немедленно. Поскольку это обновление состояния отмечено как транзиция, медленный повторный рендеринг не заблокировал пользовательский интерфейс.
import { useState, useTransition } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('about'); function selectTab(nextTab) { startTransition(() => { setTab(nextTab); }); } return ( <> <TabButton isActive={tab === 'about'} onClick={() => selectTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => selectTab('posts')} > Posts (slow) </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => selectTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </> ); }
Обновление родительского компонента в транзиции
Вы также можете обновить состояние родительского компонента из вызова useTransition. Например, этот TabButton компонент оборачивает свою логику onClick в транзицию:
export default function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
onClick();
});
}}>
{children}
</button>
);
} Поскольку родительский компонент обновляет своё состояние внутри обработчика события onClick, это обновление состояния отмечается как транзиция. Вот почему, как и в предыдущем примере, вы можете нажать на «Статьи», а затем немедленно на «Контакты». Обновление выбранной вкладки отмечается как транзиция, поэтому оно не блокирует взаимодействия пользователя.
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Отображение ожидающего визуального состояния во время транзиции
Вы можете использовать булевое значение isPending возвращаемое useTransition для указания пользователю, что транзиция в процессе. Например, кнопка вкладки может иметь специальное «ожидающее» визуальное состояние:
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ... Обратите внимание, как нажатие на «Статьи» теперь ощущается более отзывчивым, потому что сама кнопка вкладки обновляется сразу же:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Предотвращение нежелательных индикаторов загрузки
В этом примере компонент PostsTab извлекает данные с помощью Suspense-совместимого источника данных. При нажатии на вкладку «Статьи» компонент PostsTab приостанавливает работу, в результате чего появляется ближайший резервный вариант загрузки:
import { Suspense, useState } from 'react'; import TabButton from './TabButton.js'; import AboutTab from './AboutTab.js'; import PostsTab from './PostsTab.js'; import ContactTab from './ContactTab.js'; export default function TabContainer() { const [tab, setTab] = useState('about'); return ( <Suspense fallback={<h1>🌀 Loading...</h1>}> <TabButton isActive={tab === 'about'} onClick={() => setTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} onClick={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} onClick={() => setTab('contact')} > Contact </TabButton> <hr /> {tab === 'about' && <AboutTab />} {tab === 'posts' && <PostsTab />} {tab === 'contact' && <ContactTab />} </Suspense> ); }
Скрытие всего контейнера вкладки для отображения индикатора загрузки приводит к резкому пользовательскому опыту. Если добавить useTransition в TabButton, можно вместо этого отобразить состояние ожидания в кнопке вкладки.
Обратите внимание, что нажатие на «Статьи» больше не заменяет весь контейнер вкладки спиннером:
import { useTransition } from 'react'; export default function TabButton({ children, isActive, onClick }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { onClick(); }); }}> {children} </button> ); }
Дополнительная информация об использовании транзиций с Suspense.
Примечание
Транзиции будут «ждать» только достаточно долго, чтобы избежать скрытия уже отображаемого содержимого (например, контейнера вкладки). Если вкладка «Статьи» имела вложенную <Suspense> границу, транзиция не «ждала» бы её.
Создание маршрутизатора с поддержкой Suspense
Если вы разрабатываете React-фреймворк или маршрутизатор, мы рекомендуем отмечать навигации страниц как транзиции.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ... Это рекомендуется по двум причинам:
- Транзиции прерываются, что позволяет пользователю переключаться, не дожидаясь завершения повторной отрисовки.
- Транзиции предотвращают нежелательные индикаторы загрузки, что позволяет пользователю избежать резких переходов при навигации.
Вот небольшой упрощённый пример маршрутизатора, использующего транзиции для навигации.
import { Suspense, useState, useTransition } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Loading...</h2>; }
Примечание
Suspense-совместимые маршрутизаторы по умолчанию должны оборачивать обновления навигации в транзиции.
Отображение ошибки пользователю с помощью границы ошибок
Кандидат
Граница ошибок для useTransition в настоящее время доступна только в каналах React canary и experimental. Дополнительные сведения о каналах выпуска React здесь.
Если функция, переданная в startTransition , генерирует ошибку, вы можете отобразить ошибку пользователю с помощью границы ошибок. Для использования границы ошибок оберните компонент, в котором вы вызываете useTransition , в границу ошибок. Как только функция, переданная в startTransition , генерирует ошибку, будет отображаться резервный вариант для границы ошибок.
import { useTransition } from "react"; import { ErrorBoundary } from "react-error-boundary"; export function AddCommentContainer() { return ( <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}> <AddCommentButton /> </ErrorBoundary> ); } function addComment(comment) { // For demonstration purposes to show Error Boundary if (comment == null) { throw new Error("Example Error: An error thrown to trigger error boundary"); } } function AddCommentButton() { const [pending, startTransition] = useTransition(); return ( <button disabled={pending} onClick={() => { startTransition(() => { // Intentionally not passing a comment // so error gets thrown addComment(); }); }} > Add comment </button> ); }
Отладка
Обновление поля ввода в транзиции не работает
Вы не можете использовать транзицию для переменной состояния, которая управляет полем ввода:
const [text, setText] = useState('');
// ...
function handleChange(e) {
// ❌ Can't use Transitions for controlled input state
startTransition(() => {
setText(e.target.value);
});
}
// ...
return <input value={text} onChange={handleChange} />; Это происходит потому, что транзиции неблокирующие, но обновление поля ввода в ответ на событие изменения должно происходить синхронно. Если вы хотите запустить транзицию в ответ на нажатие клавиши, у вас есть два варианта:
- Вы можете объявить две отдельные переменные состояния: одну для состояния ввода (которая всегда обновляется синхронно), и одну, которую вы обновите в Transition. Это позволяет управлять вводом с помощью синхронного состояния и передавать переменную состояния Transition (которая будет «отставать» от ввода) остальной части вашей логики отрисовки.
- В качестве альтернативы, вы можете иметь одну переменную состояния и добавить
useDeferredValue, которая будет «отставать» от реального значения. Она будет вызывать неблокирующие повторные отрисовки для автоматического «догоняния» нового значения.
React не обрабатывает обновление состояния как Transition
Когда вы оборачиваете обновление состояния в Transition, убедитесь, что это происходит во время вызова startTransition.
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
}); Функция, которую вы передаете в startTransition, должна быть синхронной.
Вы не можете отметить обновление как Transition таким образом:
startTransition(() => {
// ❌ Setting state *after* startTransition call
setTimeout(() => {
setPage('/about');
}, 1000);
}); Вместо этого, вы можете сделать так:
setTimeout(() => {
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
}, 1000); Аналогично, вы не можете отметить обновление как Transition следующим образом:
startTransition(async () => {
await someAsyncFunction();
// ❌ Setting state *after* startTransition call
setPage('/about');
}); Однако, это работает вместо этого:
await someAsyncFunction();
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
}); Я хочу вызвать useTransition извне компонента
Вы не можете вызвать useTransition вне компонента, потому что это Хук. В этом случае используйте метод startTransition вместо. Он работает так же, но не предоставляет индикатор isPending.
Функция, которую я передаю в startTransition, выполняется немедленно
Если вы запустите этот код, он выведет 1, 2, 3:
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3); Ожидается вывод 1, 2, 3. Функция, которую вы передаете в startTransition, не задерживается. В отличие от браузерного setTimeout, она не выполняет обратный вызов позже. React выполняет вашу функцию немедленно, но любые обновления состояния, запланированные во время ее выполнения, помечаются как Transitions. Вы можете представить, что это работает так:
// A simplified version of how React works
let isInsideTransition = false;
function startTransition(scope) {
isInsideTransition = true;
scope();
isInsideTransition = false;
}
function setState() {
if (isInsideTransition) {
// ... schedule a Transition state update ...
} else {
// ... schedule an urgent state update ...
}
}
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/reference/react/useTransition