
它标记的是「这次更新不急」
const [isPending, startTransition] = useTransition()官方原文:useTransition 不接受任何参数,返回 [isPending, startTransition]。
这一条要专门写出来,因为网上大量文章还在教 useTransition({ timeoutMs: 1000 })。那是 React 18 实验期的写法,稳定版没有这个参数,抄进去会直接失效。
回调会立刻执行
这是最常被讲错的一点。官方给了一段会依次打印 1、2、3 的代码:
console.log(1)startTransition(() => { console.log(2) setPage('/about')})console.log(3)startTransition 不会把函数推到「主线程空闲时」再跑。React 立刻调用它,被标记成过渡的只是它执行期间安排的 state 更新。所以回调里不要写任何依赖「执行时机」的逻辑。
它带来的两件事
过渡更新是可中断的。用户在切换视图的过程中继续输入,React 会放弃这次渲染,先去处理输入。
过渡更新不会把已经显示的内容换成加载态。配合 Suspense 时,它会在已显示内容的边界内等新内容就绪,而不是先闪一下 fallback。
什么时候用它
典型场景是「切换一个视图,新视图渲染慢」。用户点了标签页,输入框和其他部分仍然要立刻响应。
function TabContainer() { const [tab, setTab] = useState('about') const [isPending, startTransition] = useTransition()
function selectTab(nextTab) { startTransition(() => { setTab(nextTab) }) }
return ( <> <TabButton isActive={tab === 'about'} action={() => selectTab('about')}>关于</TabButton> <TabButton isActive={tab === 'posts'} action={() => selectTab('posts')}> 文章{isPending && '(加载中)'} </TabButton> </> )}isPending 从第一次调用 startTransition 起变为 true,直到所有过渡完成才回到 false。它适合做按钮上的小状态,而不是整页的加载图标。
不能控制文本输入
过渡更新不能用来控制输入框的值,输入必须同步更新:
// 不行:输入会变得一顿一顿startTransition(() => { setText(e.target.value)})想做到「输入立即响应、结果列表慢一点」,用 useDeferredValue。
await 之后要再包一层
startTransition(async () => { await save() startTransition(() => setStatus('done')) // 不包,这次更新不算过渡})await 之后的 state 更新不再算过渡,因为 React 在这个边界上丢了上下文。官方说这是当前的已知限制,将来会修。
官方更推荐的高层写法
useTransition 参考页里点名:常见的异步提交优先用 useActionState、<form> 的 action、Server Functions,它们会替你处理请求顺序。自己包 startTransition 一般是写自定义 Hook 或库的时候才需要。
四条边界
回调里的计算是同步跑的。过渡只推迟「状态更新引起的渲染」,不会把回调里那段筛选或计算变快。所以「筛选一万条数据」的开销放在回调里并不能被推迟,那种场景要用 useDeferredValue 或把慢组件抽出来。
回调抛错或返回 reject 的 Promise 时,用错误边界来接。官方的用法就是外面包一层 ErrorBoundary。
同时有多个过渡时,React 目前会把它们合并处理,这是已知限制,将来可能改。
它需要 React 18 及以上。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时