【51CTO.com快译】本文将向您介绍React并发模式背后的思想,以及它的一些用法和优点。React的并发模式是一组创新的特性,旨在改进异步呈现的处理。这些改进带来了更好的终端用户体验。
如何让Web客户端呈现异步更新是业内一直面临的问题。React团队通过向React 16.x中添加并发模式支持,延续了将优秀解决方案引入框架的传统。
在很多情况下,对状态变化的幼稚呈现会导致不太理想的行为,例如乏味的加载屏幕、不稳定的输入处理和不必要的旋转。
零零碎碎地解决这些问题是容易出错的。React的并发模式代表了一种全面的,框架化的解决方案。其核心思想是:React在内存中并发地绘制更新,支持可中断的渲染,并提供了应用程序代码与这种支持交互的方式。
在React中启用并发模式
用于利用这些功能的API仍在不断变化,你必须明确地安装它,如下所示:
npm install react @ experimental react - dom @ experimental
并发模式是对React工作方式的全局更改,并要求根节点通过并发引擎传递。这是通过在应用的根目录上调用createRoot来完成的,而不是仅仅调用reactDOM.render()。如Listing 1所示。
Listing 1. Using the concurrent renderer
ReactDOM.createRoot(
document.getElementById('root')
).render(<App />);
需要注意的是,createRoot只有在安装了实验包之后才可用。而且因为它是一个根本性的变化,现有的代码库和库可能与它不兼容。特别是现在用UNSAFE_作为前缀的生命周期方法是不兼容的。
正因为如此,React在我们现在使用的老式渲染引擎和并发模式之间引入了一个中间步骤。这个步骤称为“阻塞模式”,它向后兼容,但并发特性更少。
从长远来看,并发模式将成为默认模式。在中期,React将支持以下三种模式:
1. 旧版模式:ReactDOM.render(<App />, rootNode)。现有的旧版模式。
2. 封锁模式:ReactDOM.createBlockingRoot(rootNode).render(<App />)。更少的改变,更少的特性。
3. 并发模式:ReactDOM.createRoot(rootNode).render(<App />)。完全并发模式,具有许多重大的更改。
React中的一个新的渲染模型
并发模式从根本上改变了React渲染接口的方式,以允许在获取数据的过程中渲染接口。这意味着React必须知道一些关于组件的信息。具体来说,React现在必须知道组件的数据获取状态。
React的新Suspense组件
新Suspense组件是最突出的功能,您可以使用此组件通知React UI的给定区域依赖于异步数据加载,并向其提供此类加载的状态。
这个功能是在框架级别上执行的,这意味着您的数据获取库必须通过实现Suspense API来警告React的状态。目前,Relay为GraphQL执行此操作,而react- suspend -fetch项目处理REST数据的获取。
重申一下,现在需要使用更智能的数据获取库,该库能够告诉React它的状态是什么,从而允许React优化UI的呈现方式。如下Listing 2包含了重要的视图模板细节。
Listing 2. Using Suspense in the view
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
注意,这 Suspense 允许定义备用加载内容。这类似于您如何根据旧渲染引擎中组件的加载状态在组件内部使用不同的返回值来渲染占位符,直到数据准备就绪。
在这个视图模板使用的组件中,不需要任何特殊代码即可处理加载状态。现在,所有这些都由框架和数据提取库在后台处理。
例如,ProfileDetails组件可以加载它的数据并返回它的标记,如Listing 3所示。同样,这取决于resource实现Suspense API的数据存储(在Listing 3中是resource对象)。
Listing 3. ProfileDetails
function ProfileDetails() {
const user = resource.user.read();
return <h1>{user.name}</h1>;
}
数据获取中的并发性
这种设置的一个重要好处是,所有的数据获取都可以并发进行。因此,你的UI既受益于改进的渲染生命周期,也受益于为多个组件实现并行数据获取的简单而自动的方法。
React的useTransition
新的concurrent React工具包中的下一个主要工具是useTransition。这是一个更细粒度的工具,允许你调整UI转换的发生方式。Listing 4给出了一个使用useTransition包装转换的示例。
Listing 4. useTransition
function App() {
const [resource, setResource] = useState(initialResource);
const [ startTransition, isPending ] = useTransition({ timeoutMs: 3000 });
return (
<>
<button
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
});
}}> Next </button>
{isPending ? " Loading..." : null}
<ProfilePage resource={resource} />
</>
);
}
Listing 4中的代码表示,“将新状态的显示延迟到三秒。”这段代码之所以能够工作,是因为ProfilePage在使用时是由Suspense组件包装的。React开始获取数据,并且不显示占位符,而是将现有内容显示已定义的时间timeoutMs。提取完成后,React将显示更新的内容。这是一种用于改善过渡效果的简单机制。
useTransition公开的startTransition函数允许你包装代码的获取部分,而isPending函数公开一个布尔标志,你可以使用它来处理条件加载显示。
所有这一切之所以成为可能,是因为React的并发模式实现了一种后台呈现机制:当获取发生时,React会在后台的内存中呈现更新后的状态UI。
React的useDeferredValue
最后一个例子涉及修复类型导致数据加载时不一致的类型问题。这是一个典型的问题,通常通过输入的反弹/节流来解决。并发模式提供了一个更一致、更流畅的解决方案:useDeferredValue。
举个例子。这个解决方案的天才之处在于它能让你两全其美。输入保持响应状态,并且列表在数据可用时立即更新。
Listing 5. useDeferredValue in action
const [text, setText] = useState("hello");
const deferredText = useDeferredValue(text, { timeoutMs: 5000 });
// ....
<MySlowList text={deferredText} />
与我们使用useTransition包装转换的方式类似,我们使用useDeferredValue包装资源值。这允许值在timeoutMs期间保持不变。管理此改进的渲染的所有复杂性均由React和数据存储在后台处理。
使用Suspense和并发模式的另一个好处是,避免了在生命周期和方法中手动加载数据而引入的竞争条件。保证数据到达并按照请求的顺序应用。因此,新模式避免了由于请求响应的交错而手工检查数据过时性的需要。
这些是新并发模式的一些亮点。它们提供了令人信服的好处,这将成为未来的常态。
【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】