参考
useTransition()
在组件顶层调用 useTransition
,将某些状态更新标记为 transition。
。
参数
useTransition
不需要任何参数。
返回值
useTransition
返回一个由两个元素组成的数组:
isPending
,告诉你是否存在待处理的 transition。- ,你可以使用此方法将更新标记为 transition。
startTransition
函数
useTransition
返回的 startTransition
函数允许你将更新标记为 Transition。
参数
action
:通过调用一个或多个 来更新某些状态的函数。React 会立即调用action
(无需参数),并将action
函数调用期间同步调度的所有状态更新标记为 Transition。在action
中通过await
等待的异步调用会被包含在 Transition 中,但目前需要在await
之后将任何set
函数再次包裹在startTransition
中(参见)。标记为 Transition 的状态更新将具备,并且。
返回值
startTransition
不返回任何值。
注意
useTransition
是一个 Hook,因此只能在组件或自定义 Hook 内部调用。如果需要在其他地方启动 transition(例如从数据库),请调用独立的 函数。只有在可以访问该状态的
set
函数时,才能将其对应的状态更新包装为 transition。如果你想启用 Transition 以响应某个 prop 或自定义 Hook 值,请尝试使用 。传递给
startTransition
的函数会被立即执行,并将在其执行期间发生的所有状态更新标记为 transition。如果你尝试在setTimeout
中执行状态更新,它们将不会被标记为 transition。你必须将任意异步请求之后的状态更新用
startTransition
包裹,以将其标记为 Transition 更新。这是一个已知限制,我们将在未来版本中修复(参见)。startTransition
函数具有稳定的标识,所以你经常会看到 Effect 的依赖数组中会省略它,即使包含它也不会导致 Effect 重新触发。如果 linter 允许你省略依赖项并且没有报错,那么你就可以安全地省略它。标记为 Transition 的状态更新将被其他状态更新打断。例如在 Transition 中更新图表组件,并在图表组件仍在重新渲染时继续在输入框中输入,React 将首先处理输入框的更新,之后再重新启动对图表组件的渲染工作。
Transition 更新不能用于控制文本输入。
目前,React 会批处理多个同时进行的 transition。这是一个限制,可能会在未来版本中删除。
用法
通过 Action 执行非阻塞更新
在组件的顶层调用 useTransition
来创建 Action,并获取挂起的状态:
useTransition
返回一个由两个元素组成的数组:
isPending
,告诉你是否存在待处理的 transition。startTransition
函数,你可以使用此方法创建一个 Action。
为了启动 Transition,你需要将函数传递给 startTransition
。例如:
传递给 startTransition
的函数被称为 “Action”。你可以在 Action 中更新状态和执行副作用操作,这些工作将在后台执行,不会阻塞页面的用户交互。一个 Transition 可以包含多个 Action,且在 Transition 进行期间,你的用户界面将保持流畅响应。例如,如果用户点击一个标签页后又改变主意点击另一个标签页,第二个点击会立即被处理,无需等待第一个更新完成。
为了向用户提供 Transition 进行中的反馈, isPending
状态会在首次调用 startTransition
时切换为 true
,并会在所有 Action 完成且最终状态呈现给用户前一直保持为 true
。Transition 机制确保 Action 中的副作用会完整执行以,同时你可以通过 useOptimistic
在 Transition 进行期间提供即时反馈。
在组件中公开 action
属性
你可以通过组件暴露一个 action
属性,允许父组件调用一个 Action。
例如,这个 TabButton
组件将 onClick
事件逻辑封装到 action
属性中:
由于父组件的状态更新在 action
中,所以该状态更新会被标记为 transition。这意味着你可以在点击“Posts”后立即点击“Contact”,并且它不会阻止用户交互:
显示待处理的视觉状态
你可以使用 useTransition
返回的 isPending
布尔值来向用户表明当前处于 Transition 中。例如,选项卡按钮可以有一个特殊的“pending”视觉状态:
请注意,现在点击“Posts”感觉更加灵敏,因为选项卡按钮本身立即更新了:
避免不必要的加载指示器
在这个例子中,PostsTab
组件通过 获取了一些数据。当你点击“Posts”选项卡时,PostsTab
组件将 挂起,导致使用最近的加载中的后备方案:
隐藏整个选项卡容器以显示加载指示符会导致用户体验不连贯。如果你将 useTransition
添加到 TabButton
中,你可以改为在选项卡按钮中指示待处理状态。
请注意,现在点击“帖子”不再用一个旋转器替换整个选项卡容器:
。
构建一个Suspense-enabled 的路由
如果你正在构建一个 React 框架或路由,我们建议将页面导航标记为转换效果。
这么做有三个好处:
- ,这样用户可以在等待重新渲染完成之前点击其他地方。
- ,这样用户就可以避免在导航时产生不协调的跳转。
- ,它允许用户在副作用完成之后再显示新页面。
下面是一个简单的使用转换效果进行页面导航的路由器示例:
使用错误边界向用户显示错误
如果传递给 startTransition
的函数抛出错误,可以通过 向用户显示错误。要使用错误边界,请将调用 useTransition
的组件包裹在错误边界中。当传递给 startTransition
的函数报错时,错误边界的备用 UI 将会显示。
疑难解答
在 Transition 中无法更新输入框内容
不应将控制输入框的状态变量标记为 transition:
这是因为 Transition 是非阻塞的,但是在响应更改事件时更新输入应该是同步的。如果想在输入时运行一个 transition,那么有两种做法:
- 声明两个独立的状态变量:一个用于输入状态(它总是同步更新),另一个用于在 Transition 中更新。这样,便可以使用同步状态控制输入,并将用于 Transition 的状态变量(它将“滞后”于输入)传递给其余的渲染逻辑。
- 或者使用一个状态变量,并添加 ,它将“滞后”于实际值,并自动触发非阻塞的重新渲染以“追赶”新值。
React 没有将状态更新视为 Transition
当在 Transition 中包装状态更新时,请确保它发生在 startTransition
调用期间:
传递给 startTransition
的函数必须是同步的。你不能像这样将更新标记为 transition:
相反,你可以这样做:
React 不会将 await
之后的状态更新视为 Transition
当你在 startTransition
函数内部使用 await
时,await
之后的状态更新不会被标记为 Transition 更新。你必须将每个 await
之后的状态更新再次包裹在 startTransition
调用中:
然而,使用以下方法可以正常工作:
这是由于 JavaScript 的限制,React 无法跟踪异步上下文的范围。未来当 提案实现后,该限制将被消除。
我想在组件外部调用 useTransition
useTransition
是一个 Hook,因此不能在组件外部调用。请使用独立的 方法。它们的工作方式相同,但不提供 isPending
标记。
我传递给 startTransition
的函数会立即执行
如果你运行这段代码,它将会打印 1, 2, 3:
期望打印 1, 2, 3。传递给 startTransition
的函数不会被延迟执行。与浏览器的 setTimeout
不同,它不会延迟执行回调。React 会立即执行你的函数,但是在它运行的同时安排的任何状态更新都被标记为 transition。你可以将其想象为以下方式:
Transitions 中的状态更新顺序混乱
如果在 startTransition
内部使用 await
,你可能会看到更新出现顺序错乱。
在这个示例中,updateQuantity
函数模拟向服务端发送请求以更新购物车中的商品数量。该函数人为地让每隔一次请求在前一次之后返回,用于模拟网络请求的竞态条件。
尝试更新一次数量,然后快速多次更新。你可能会看到错误的总计:
多次点击时,较早的请求可能会在较晚的请求之后完成。当这种情况发生时,React 目前无法知道预期的顺序。这是因为更新是异步调度的,而 React 在异步边界处丢失了顺序的上下文。
这是预期内的,因为在 Transition 中的 Action 不保证执行顺序。对于常见用例,React 提供了更高级的抽象,如 和 来为你处理顺序问题。对于高级用例,你需要自行实现队列和中止逻辑来处理这种情况。
Example of useActionState
handling execution order: