useTransition
是一个 React Hook,可让你在后台渲染部分 UI。
¥useTransition
is a React Hook that lets you render a part of the UI in the background.
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. -
可让你将更新标记为 Transition 的
startTransition
函数。¥The
startTransition
function that lets you mark updates as a Transition.
startTransition(action)
useTransition
返回的 startTransition
函数可让你将更新标记为 Transition。
¥The startTransition
function returned by useTransition
lets you mark a updates as a Transition.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
参数
¥Parameters
-
action
:通过调用一个或多个set
函数 来更新某些状态的函数。React 立即调用action
,不带任何参数,并将action
函数调用期间同步安排的所有状态更新标记为 Transitions。action
中等待的任何异步调用都将包含在转换中,但目前需要在额外的startTransition
中封装await
之后的任何set
函数(参见 故障排除)。标记为 Transitions 的状态更新将是 non-blocking 和 不会显示不必要的加载指示器。¥
action
: A function that updates some state by calling one or moreset
functions. React callsaction
immediately with no parameters and marks all state updates scheduled synchronously during theaction
function call as Transitions. Any async calls that are awaited in theaction
will be included in the Transition, but currently require wrapping anyset
functions after theawait
in an additionalstartTransition
(see Troubleshooting). State updates marked as Transitions 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
的函数会被立即调用,将其执行时发生的所有状态更新标记为 Transitions。例如,如果你尝试在setTimeout
中执行状态更新,它们将不会被标记为 Transitions。¥The function you pass to
startTransition
is called immediately, marking all state updates that happen while it executes as Transitions. If you try to perform state updates in asetTimeout
, for example, they won’t be marked as Transitions. -
你必须在任何异步请求之后将任何状态更新封装在另一个
startTransition
中,以将它们标记为转换。这是一个已知的限制,我们将在未来修复(请参阅 故障排除)。¥You must wrap any state updates after any async requests in another
startTransition
to mark them as Transitions. This is a known limitation that we will fix in the future (see Troubleshooting). -
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 may be removed in a future release.
用法
¥Usage
使用 Actions 执行非阻塞更新
¥Perform non-blocking updates with Actions
在组件顶部调用 useTransition
以创建操作并访问待处理状态:
¥Call useTransition
at the top of your component to create Actions, and access the pending state:
import {useState, useTransition} from 'react';
function CheckoutForm() {
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
函数 可让你创建一个操作。¥The
startTransition
function that lets you create an Action.
要启动转换,请像这样将函数传递给 startTransition
:
¥To start a Transition, pass a function to startTransition
like this:
import {useState, useTransition} from 'react';
import {updateQuantity} from './api';
function CheckoutForm() {
const [isPending, startTransition] = useTransition();
const [quantity, setQuantity] = useState(1);
function onSubmit(newQuantity) {
startTransition(async function () {
const savedQuantity = await updateQuantity(newQuantity);
startTransition(() => {
setQuantity(savedQuantity);
});
});
}
// ...
}
传递给 startTransition
的函数称为 “操作”。你可以在操作中更新状态并(可选)执行副作用,并且工作将在后台完成,而不会阻止页面上的用户交互。Transition 可以包含多个 Action,当 Transition 正在进行时,你的 UI 保持响应。例如,如果用户单击一个选项卡,但随后改变主意并单击另一个选项卡,则第二次单击将立即处理,而无需等待第一次更新完成。
¥The function passed to startTransition
is called the “Action”. You can update state and (optionally) perform side effects within an Action, and the work will be done in the background without blocking user interactions on the page. A Transition can include multiple Actions, and while a Transition is in progress, your UI stays responsive. For example, if the user clicks a tab but then changes their mind and clicks another tab, the second click will be immediately handled without waiting for the first update to finish.
为了向用户提供有关正在进行的转换的反馈,在第一次调用 startTransition
时,isPending
状态切换到 true
,并保持 true
,直到所有操作完成并向用户显示最终状态。转换确保操作中的副作用按 防止不必要的加载指示器 完成,并且你可以在转换正在进行时使用 useOptimistic
提供即时反馈。
¥To give the user feedback about in-progress Transitions, to isPending
state switches to true
at the first call to startTransition
, and stays true
until all Actions complete and the final state is shown to the user. Transitions ensure side effects in Actions to complete in order to prevent unwanted loading indicators, and you can provide immediate feedback while the Transition is in progress with useOptimistic
.
例子 1 / 2: 在操作中更新数量
¥Updating the quantity in an Action
在此示例中,updateQuantity
函数模拟向服务器发出请求以更新购物车中的商品数量。此函数被人为地减慢速度,因此至少需要一秒钟才能完成请求。
¥In this example, the updateQuantity
function simulates a request to the server to update the item’s quantity in the cart. This function is artificially slowed down so that it takes at least a second to complete the request.
快速多次更新数量。请注意,任何请求正在进行时都会显示待处理的 “总计” 状态,并且只有在最终请求完成后才会更新 “总计”。因为更新是在 Action 中,所以 “quantity” 可以在请求进行时继续更新。
¥Update the quantity multiple times quickly. Notice that the pending “Total” state is shown while any requests are in progress, and the “Total” updates only after the final request is complete. Because the update is in an Action, the “quantity” can continue to be updated while the request is in progress.
import { useState, useTransition } from "react"; import { updateQuantity } from "./api"; import Item from "./Item"; import Total from "./Total"; export default function App({}) { const [quantity, setQuantity] = useState(1); const [isPending, startTransition] = useTransition(); const updateQuantityAction = async newQuantity => { // To access the pending state of a transition, // call startTransition again. startTransition(async () => { const savedQuantity = await updateQuantity(newQuantity); startTransition(() => { setQuantity(savedQuantity); }); }); }; return ( <div> <h1>Checkout</h1> <Item action={updateQuantityAction}/> <hr /> <Total quantity={quantity} isPending={isPending} /> </div> ); }
这是一个演示 Actions 如何工作的基本示例,但此示例不处理无序完成的请求。多次更新数量时,前面的请求可能会在后面的请求之后完成,从而导致数量更新无序。这是一个已知的限制,我们将在未来修复(请参阅下面的 故障排除)。
¥This is a basic example to demonstrate how Actions work, but this example does not handle requests completing out of order. When updating the quantity multiple times, it’s possible for the previous requests to finish after later requests causing the quantity to update out of order. This is a known limitation that we will fix in the future (see Troubleshooting below).
对于常见用例,React 提供内置抽象,例如:
¥For common use cases, React provides built-in abstractions such as:
这些解决方案为你处理请求排序。使用 Transitions 构建自己的自定义钩子或管理异步状态转换的库时,你可以更好地控制请求顺序,但你必须自己处理。
¥These solutions handle request ordering for you. When using Transitions to build your own custom hooks or libraries that manage async state transitions, you have greater control over the request ordering, but you must handle it yourself.
从组件公开 action
prop
¥Exposing action
prop from components
你可以从组件中公开 action
prop,以允许父级调用操作。
¥You can expose an action
prop from a component to allow a parent to call an Action.
例如,这个 TabButton
组件将其 onClick
逻辑封装在 action
属性中:
¥For example, this TabButton
component wraps its onClick
logic in an action
prop:
export default function TabButton({ action, children, isActive }) {
const [isPending, startTransition] = useTransition();
if (isActive) {
return <b>{children}</b>
}
return (
<button onClick={() => {
startTransition(() => {
action();
});
}}>
{children}
</button>
);
}
由于父组件在 action
内更新其状态,因此该状态更新被标记为转换。这意味着你可以单击 “帖子”,然后立即单击 “联系”,它不会阻止用户交互:
¥Because the parent component updates its state inside the action
, that state update gets marked as a Transition. This means you can click on “Posts” and then immediately click “Contact” and it does not block user interactions:
import { useTransition } from 'react'; export default function TabButton({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
显示待处理的视觉状态
¥Displaying a pending visual state
你可以使用 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({ action, children, isActive }) {
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({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {children} </button> ); }
防止不需要的加载指示器
¥Preventing unwanted loading indicators
在此示例中,PostsTab
组件使用 use 获取一些数据。当你单击 “帖子” 选项卡时,PostsTab
组件会暂停,从而导致出现最接近的加载回退:
¥In this example, the PostsTab
component fetches some data using use. 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'} action={() => setTab('about')} > About </TabButton> <TabButton isActive={tab === 'posts'} action={() => setTab('posts')} > Posts </TabButton> <TabButton isActive={tab === 'contact'} action={() => 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 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({ action, children, isActive }) { const [isPending, startTransition] = useTransition(); if (isActive) { return <b>{children}</b> } if (isPending) { return <b className="pending">{children}</b>; } return ( <button onClick={() => { startTransition(() => { action(); }); }}> {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 three 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 wait for all pending actions which lets the user wait for side effects to complete before the new page is shown.
这是一个使用转换进行导航的简化路由示例。
¥Here is a 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);
React 不会将 await
之后的状态更新视为转换
¥React doesn’t treat my state update after await
as a Transition
当你在 startTransition
函数中使用 await
时,await
之后发生的状态更新不会标记为转换。你必须在 startTransition
调用中的每个 await
之后封装状态更新:
¥When you use await
inside a startTransition
function, the state updates that happen after the await
are not marked as Transitions. You must wrap state updates after each await
in a startTransition
call:
startTransition(async () => {
await someAsyncFunction();
// ❌ Not using startTransition after await
setPage('/about');
});
但是,改成这样是有效的:
¥However, this works instead:
startTransition(async () => {
await someAsyncFunction();
// ✅ Using startTransition *after* await
startTransition(() => {
setPage('/about');
});
});
这是由于 React 丢失了异步上下文的范围而导致的 JavaScript 限制。将来,当 AsyncContext 可用时,此限制将被删除。
¥This is a JavaScript limitation due to React losing the scope of the async context. In the future, when AsyncContext is available, this limitation will be removed.
我想从组件外部调用 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 ...
}
}
我的 Transitions 中的状态更新无序
¥My state updates in Transitions are out of order
如果你在 startTransition
内部使用 await
,你可能会看到更新发生顺序错误。
¥If you await
inside startTransition
, you might see the updates happen out of order.
在此示例中,updateQuantity
函数模拟向服务器发出请求以更新购物车中的商品数量。此函数人为地返回前一个请求之后的每个其他请求,以模拟网络请求的竞争条件。
¥In this example, the updateQuantity
function simulates a request to the server to update the item’s quantity in the cart. This function artificially returns the every other request after the previous to simulate race conditions for network requests.
尝试更新数量一次,然后快速更新多次。你可能会看到不正确的总数:
¥Try updating the quantity once, then update it quickly multiple times. You might see the incorrect total:
import { useState, useTransition } from "react"; import { updateQuantity } from "./api"; import Item from "./Item"; import Total from "./Total"; export default function App({}) { const [quantity, setQuantity] = useState(1); const [isPending, startTransition] = useTransition(); // Store the actual quantity in separate state to show the mismatch. const [clientQuantity, setClientQuantity] = useState(1); const updateQuantityAction = newQuantity => { setClientQuantity(newQuantity); // Access the pending state of the transition, // by wrapping in startTransition again. startTransition(async () => { const savedQuantity = await updateQuantity(newQuantity); startTransition(() => { setQuantity(savedQuantity); }); }); }; return ( <div> <h1>Checkout</h1> <Item action={updateQuantityAction}/> <hr /> <Total clientQuantity={clientQuantity} savedQuantity={quantity} isPending={isPending} /> </div> ); }
多次单击时,先前的请求可能会在后续请求之后完成。发生这种情况时,React 目前无法知道预期的顺序。这是因为更新是异步安排的,而 React 会在异步边界上丢失顺序上下文。
¥When clicking multiple times, it’s possible for previous requests to finish after later requests. When this happens, React currently has no way to know the intended order. This is because the updates are scheduled asynchronously, and React loses context of the order across the async boundary.
这是预料之中的,因为转换内的操作不能保证执行顺序。对于常见用例,React 提供更高级的抽象,如 useActionState
和 <form>
操作,为你处理排序。对于高级用例,你需要实现自己的排队和中止逻辑来处理这个问题。
¥This is expected, because Actions within a Transition do not guarantee execution order. For common use cases, React provides higher-level abstractions like useActionState
and <form>
actions that handle ordering for you. For advanced use cases, you’ll need to implement your own queuing and abort logic to handle this.