useTransition
是一个 React 钩子,可让你在不阻塞 UI 的情况下更新状态。
¥useTransition
is a React Hook that lets you update the state without blocking the UI.
const [isPending, startTransition] = useTransition()
参考
¥Reference
useTransition()
在组件的顶层调用 useTransition
将某些状态更新标记为转换。
¥Call useTransition
at the top level of your component to mark some state updates as Transitions.
import { useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
参数
¥Parameters
useTransition
没有参数。
¥useTransition
does not take any parameters.
返回
¥Returns
useTransition
返回一个恰好包含两项的数组:
¥useTransition
returns an array with exactly two items:
-
isPending
标志告诉你是否有待处理的转换。¥The
isPending
flag that tells you whether there is a pending Transition. -
startTransition
函数 允许你将状态更新标记为转换。¥The
startTransition
function that lets you mark a state update as a Transition.
startTransition
函数
¥startTransition
function
useTransition
返回的 startTransition
函数允许你将状态更新标记为转换。
¥The startTransition
function returned by useTransition
lets you mark a state update as a Transition.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
参数
¥Parameters
-
scope
:通过调用一个或多个set
函数 React 来更新某些状态的函数会立即调用不带参数的scope
,并将在scope
函数调用期间同步安排的所有状态更新标记为 Transitions。它们将是 non-blocking 且 不会显示不需要的加载指示器。¥
scope
: A function that updates some state by calling one or moreset
functions. React immediately callsscope
with no parameters and marks all state updates scheduled synchronously during thescope
function call as Transitions. They will be non-blocking and will not display unwanted loading indicators.
返回
¥Returns
startTransition
不返回任何东西。
¥startTransition
does not return anything.
注意事项
¥Caveats
-
useTransition
是一个钩子,只能在组件内部或者自定义的钩子中调用。如果你需要在其他地方启动 Transition(例如,从数据库中启动),请改为调用独立的startTransition
。¥
useTransition
is a Hook, so it can only be called inside components or custom Hooks. If you need to start a Transition somewhere else (for example, from a data library), call the standalonestartTransition
instead. -
只有当你有权访问该状态的
set
函数时,你才能将更新封装到转换中。如果你想在响应某些属性或自定义钩子值时启动 Transition,请改为尝试useDeferredValue
。¥You can wrap an update into a Transition only if you have access to the
set
function of that state. If you want to start a Transition in response to some prop or a custom Hook value, tryuseDeferredValue
instead. -
你传递给
startTransition
的函数必须是同步的。React 立即执行此函数,将其执行时发生的所有状态更新标记为 Transitions。如果你稍后尝试执行更多状态更新(例如,在超时时),它们将不会被标记为 Transition。¥The function you pass to
startTransition
must be synchronous. React immediately executes this function, marking all state updates that happen while it executes as Transitions. If you try to perform more state updates later (for example, in a timeout), they won’t be marked as Transitions. -
startTransition
函数具有稳定的标识,因此你经常会看到它从效果依赖中省略,但包含它不会导致效果触发。如果 linter 允许你在没有错误的情况下省略依赖,那么这样做是安全的。详细了解如何删除副作用依赖。¥The
startTransition
function has a stable identity, so you will often see it omitted from effect dependencies, but including it will not cause the effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. Learn more about removing Effect dependencies. -
标记为 Transition 的状态更新将被其他状态更新打断。例如,如果你在转换中更新图表组件,但在图表处于重新渲染过程中时开始输入输入,则 React 将在处理输入更新后重新启动图表组件上的渲染工作。
¥A state update marked as a Transition will be interrupted by other state updates. For example, if you update a chart component inside a Transition, but then start typing into an input while the chart is in the middle of a re-render, React will restart the rendering work on the chart component after handling the input update.
-
转场更新不能用于控制文本输入。
¥Transition updates can’t be used to control text inputs.
-
如果有多个正在进行的 Transition,React 当前会将它们批量处理在一起。这是一个可能会在未来版本中删除的限制。
¥If there are multiple ongoing Transitions, React currently batches them together. This is a limitation that will likely be removed in a future release.
用法
¥Usage
将状态更新标记为非阻塞转换
¥Marking a state update as a non-blocking Transition
在组件的顶层调用 useTransition
,将状态更新标记为非阻塞转换。
¥Call useTransition
at the top level of your component to mark state updates as non-blocking Transitions.
import { useState, useTransition } from 'react';
function TabContainer() {
const [isPending, startTransition] = useTransition();
// ...
}
useTransition
返回一个恰好包含两项的数组:
¥useTransition
returns an array with exactly two items:
-
isPending
标志 告诉你是否有待处理的 Transition。¥The
isPending
flag that tells you whether there is a pending Transition. -
startTransition
函数 可让你将状态更新标记为 Transition。¥The
startTransition
function that lets you mark a state update as a Transition.
然后,你可以像这样将状态更新标记为转换:
¥You can then mark a state update as a Transition like this:
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
转场让你即使在速度较慢的设备上也能保持用户界面更新的响应。
¥Transitions let you keep the user interface updates responsive even on slow devices.
使用转换,你的 UI 在重新渲染过程中保持响应。例如,如果用户单击一个选项卡但随后改变主意并单击另一个选项卡,则它们可以在不等待第一次重新渲染完成的情况下执行此操作。
¥With a Transition, your UI stays responsive in the middle of a re-render. For example, if the user clicks a tab but then change their mind and click another tab, they can do that without waiting for the first re-render to finish.
例子 1 / 2: 在 Transition 中更新当前选项卡
¥Updating the current tab in a Transition
在此示例中,“帖子” 选项卡被人为减慢,因此渲染至少需要一秒钟。
¥In this example, the “Posts” tab is artificially slowed down so that it takes at least a second to render.
单击 “帖子”,然后立即单击 “联系”。请注意,这会中断 “帖子” 的缓慢渲染。“联系” 选项卡立即显示。由于此状态更新被标记为转换,因此缓慢的重新渲染不会冻结用户界面。
¥Click “Posts” and then immediately click “Contact”. Notice that this interrupts the slow render of “Posts”. The “Contact” tab shows immediately. Because this state update is marked as a Transition, a slow re-render did not freeze the user interface.
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 />} </> ); }
在 Transition 中更新父组件
¥Updating the parent component in a Transition
你也可以通过 useTransition
调用更新父组件的状态。例如,此 TabButton
组件将其 onClick
逻辑封装在 Transition 中:
¥You can update a parent component’s state from the useTransition
call, too. For example, this TabButton
component wraps its onClick
logic in a Transition:
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
事件处理程序中更新其状态,因此该状态更新被标记为转换。这就是为什么,就像在前面的示例中一样,你可以单击 “帖子”,然后立即单击 “联系”。更新选定的选项卡被标记为转换,因此它不会阻止用户交互。
¥Because the parent component updates its state inside the onClick
event handler, that state update gets marked as a Transition. This is why, like in the earlier example, you can click on “Posts” and then immediately click “Contact”. Updating the selected tab is marked as a Transition, so it does not block user interactions.
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> ); }
在转换期间显示待处理的视觉状态
¥Displaying a pending visual state during the Transition
你可以使用 useTransition
返回的 isPending
布尔值向用户指示转换正在进行中。例如,选项卡按钮可以有一个特殊的 “挂起” 视觉状态:
¥You can use the isPending
boolean value returned by useTransition
to indicate to the user that a Transition is in progress. For example, the tab button can have a special “pending” visual state:
function TabButton({ children, isActive, onClick }) {
const [isPending, startTransition] = useTransition();
// ...
if (isPending) {
return <b className="pending">{children}</b>;
}
// ...
请注意,现在单击 “帖子” 感觉更加灵敏,因为选项卡按钮本身会立即更新:
¥Notice how clicking “Posts” now feels more responsive because the tab button itself updates right away:
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> ); }
防止不需要的加载指示器
¥Preventing unwanted loading indicators
在此示例中,PostsTab
组件使用 启用 Suspense 数据源获取一些数据。当你单击 “帖子” 选项卡时,PostsTab
组件会暂停,从而导致出现最接近的加载回退:
¥In this example, the PostsTab
component fetches some data using a Suspense-enabled data source. When you click the “Posts” tab, the PostsTab
component suspends, causing the closest loading fallback to appear:
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
,则可以改为指示在选项卡按钮中显示挂起状态。
¥Hiding the entire tab container to show a loading indicator leads to a jarring user experience. If you add useTransition
to TabButton
, you can instead indicate display the pending state in the tab button instead.
请注意,单击 “帖子” 不再将整个选项卡容器替换为加载控件:
¥Notice that clicking “Posts” no longer replaces the entire tab container with a spinner:
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> ); }
¥Read more about using Transitions with Suspense.
构建一个启用 Suspense 的路由
¥Building a Suspense-enabled router
如果你正在构建 React 框架或路由,我们建议将页面导航标记为 Transitions。
¥If you’re building a React framework or a router, we recommend marking page navigations as Transitions.
function Router() {
const [page, setPage] = useState('/');
const [isPending, startTransition] = useTransition();
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
推荐这样做有两个原因:
¥This is recommended for two reasons:
-
转场是可中断的, 允许用户点击离开而无需等待重新渲染完成。
¥Transitions are interruptible, which lets the user click away without waiting for the re-render to complete.
-
转场防止不需要的加载指示器, 让用户避免在导航中跳转。
¥Transitions prevent unwanted loading indicators, which lets the user avoid jarring jumps on navigation.
这是一个使用 Transitions 进行导航的小型简化路由示例。
¥Here is a tiny simplified router example using Transitions for navigations.
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>; }
向用户显示带有错误边界的错误
¥Displaying an error to users with an error boundary
如果传递给 startTransition
的函数抛出错误,你可以使用 误差边界 向用户显示错误。要使用错误边界,请将调用 useTransition
的组件封装在错误边界中。一旦函数传递到 startTransition
错误,就会显示错误边界的回退。
¥If a function passed to startTransition
throws an error, you can display an error to your user with an error boundary. To use an error boundary, wrap the component where you are calling the useTransition
in an error boundary. Once the function passed to startTransition
errors, the fallback for the error boundary will be displayed.
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> ); }
故障排除
¥Troubleshooting
在 Transition 中更新输入不起作用
¥Updating an input in a Transition doesn’t work
你不能将转换用于控制输入的状态变量:
¥You can’t use a Transition for a state variable that controls an input:
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,你有两个选择:
¥This is because Transitions are non-blocking, but updating an input in response to the change event should happen synchronously. If you want to run a Transition in response to typing, you have two options:
-
你可以声明两个单独的状态变量:一个用于输入状态(始终同步更新),另一个用于在 Transition 中更新。这允许你使用同步状态控制输入,并将转换状态变量(将 “滞后” 输入)传递给其余渲染逻辑。
¥You can declare two separate state variables: one for the input state (which always updates synchronously), and one that you will update in a Transition. This lets you control the input using the synchronous state, and pass the Transition state variable (which will “lag behind” the input) to the rest of your rendering logic.
-
或者,你可以有一个状态变量,并添加
useDeferredValue
,这将使 “滞后” 成为实际值。它将使用新值自动触发对 “赶上” 的非阻塞重新渲染。¥Alternatively, you can have one state variable, and add
useDeferredValue
which will “lag behind” the real value. It will trigger non-blocking re-renders to “catch up” with the new value automatically.
React 不会将我的状态更新视为转换
¥React doesn’t treat my state update as a Transition
当你将状态更新封装在 Transition 中时,请确保它在 startTransition
调用期间发生:
¥When you wrap a state update in a Transition, make sure that it happens during the startTransition
call:
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
你传递给 startTransition
的函数必须是同步的。
¥The function you pass to startTransition
must be synchronous.
你不能像这样将更新标记为转换:
¥You can’t mark an update as a Transition like this:
startTransition(() => {
// ❌ Setting state *after* startTransition call
setTimeout(() => {
setPage('/about');
}, 1000);
});
而是,你可以这样做:
¥Instead, you could do this:
setTimeout(() => {
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
}, 1000);
同样,你不能像这样将更新标记为转换:
¥Similarly, you can’t mark an update as a Transition like this:
startTransition(async () => {
await someAsyncFunction();
// ❌ Setting state *after* startTransition call
setPage('/about');
});
但是,改成这样是有效的:
¥However, this works instead:
await someAsyncFunction();
startTransition(() => {
// ✅ Setting state *during* startTransition call
setPage('/about');
});
我想从组件外部调用 useTransition
¥I want to call useTransition
from outside a component
你不能在组件外部调用 useTransition
,因为它是一个钩子。在这种情况下,请改用独立的 startTransition
方法。它的工作方式相同,但不提供 isPending
指标。
¥You can’t call useTransition
outside a component because it’s a Hook. In this case, use the standalone startTransition
method instead. It works the same way, but it doesn’t provide the isPending
indicator.
我传递给 startTransition
的函数立即执行
¥The function I pass to startTransition
executes immediately
如果运行此代码,它将打印 1、2、3:
¥If you run this code, it will print 1, 2, 3:
console.log(1);
startTransition(() => {
console.log(2);
setPage('/about');
});
console.log(3);
期望打印 1、2、3。你传递给 startTransition
的函数不会延迟。与浏览器 setTimeout
不同,它不会稍后运行回调。React 会立即执行你的函数,但在运行时安排的任何状态更新都标记为 Transitions。你可以想象它是这样工作的:
¥It is expected to print 1, 2, 3. The function you pass to startTransition
does not get delayed. Unlike with the browser setTimeout
, it does not run the callback later. React executes your function immediately, but any state updates scheduled while it is running are marked as Transitions. You can imagine that it works like this:
// 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 ...
}
}