发布时间:2021年9月22日
前段时间 React 官方发布了 [email protected] 版本,其主要的改动还是围绕着最近三个版本的变动重点 Concurrent Mode 来进行,不过在 18 版本中,Concurrent Mode 正式改名为 Concurrent Rendering。
与 React@17 一样的是,React 仍然没有默认开启 Concurrent 模式,而是对外提供了两个 API,React.createBlockingRoot 与 React.createRoot 来辅助半开启/完全开启 Concurrent 模式,此处借用掘金一篇文章中的图片来介绍半开启/完全开启状态下支持的 Concurrent 特性内容。

在 Concurrent 支持的一众特性中,Suspense 在网上相关的文章已经很多了,在这里就不再多说,本篇文章的重点会放在 useTransition/React.Transition 以及 useDeferredValue 这两个新特性上。
useTransition 是 React@18 版本提供的一个新的 hook,使用方式如下代码所示:
const [isPending, startTransition] = useTransition();
该 hook的返回值 与 useState 格式相同,为一个数组,其返回值代表的含义为
isPending:该值为一个 flag 变量,表明当前 行为 是否处于等待状态
startTransition:设置 行为 的方法,其接受一个回调函数作为参数,当 isPending 为 false 即 React 判断可以进行更新时,就会执行我们传入的回调函数。
使用方式如下:
startTransition(() => {
setValues(xxxx);
})该 hook 比较适合用于取代 不存在异步操作 的 debounce 方法,让我们想一下下面的例子:
我们的页面中存在一个允许用户进行输入的输入框,在页面加载完成时我们通过后端拿到了N条数据,此时我们要实现一个根据用户输入的内容对这些数据进行筛选并高亮的功能。
在之前的写法中,对于这种高频的大批量数据处理的逻辑,我们会使用 debounce 来限制数据的处理频率,比如在用户停止输入后的 300ms 后进行数据处理之类。
而在 React@18 中我们可以用 useTransition 来取代的 debounce 的作用,由 React 自己判断是否要对数据筛选的关键字 key 进行保存并触发数据的处理。
具体使用,可参考下面的代码:
import { useState, useTransition } from 'react';
export function FilterList({ names }) {
// 用户输入的过滤高亮内容
const [highlight, setHighlight] = useState('');
// 引入transition
const [isPending, startTransition] = useTransition();
// 输入框变动
const changeHandler = ({ target: { value } }) => {
// 在合适的实际设置高亮内容字段
startTransition(() => setHighlight(value));
};
return (
<div>
<input onChange={changeHandler} type="text" />
{names.map((name, i) => (
<ListItem key={i} name={name} highlight={highlight} />
))}
</div>
);
}
function ListItem({ name, highlight }) {
const index = name.toLowerCase().indexOf(highlight.toLowerCase());
if (index === -1) {
return <div>{name}</div>;
}
return (
<div>
{name.slice(0, index)}
<span className="highlight">
{name.slice(index, index + highlight.length)}
</span>
{name.slice(index + highlight.length)}
</div>
);
}
在上面我说到
该
hook比较适合用于取代 不存在异步操作 的debounce方法
之所以说这话是因为,useTransition 与传统的 debuounce 并不完全相同,其判断逻辑对我们来说是黑盒,不过大概可以猜到,我们指定 Transition 执行的时机判断肯定是与任务的权重、浏览器目前任务的多少是相关联的。
在这种情况下,何时执行 Transition 并不完全受控于我们,所以如果 Transition 中存在异步操作,那么在部分情况下肯定会导致异步请求发送时机的失控,对于这种场景,更合适的解决方案还是传统的 useEffect 以及 debounce 结合解决。
所以说 useTransition 并不适合在全场景下取代传统的 debounce 函数。
useDeferredValue ,直接翻译为使用一个推迟的值,实际上其代表着创建一个与某一个变量相关联的新变量,当其关联的变量发生变动时,React 框架将在何时的时机将最新的值同步到使用 useDeferredValue 创建的新变量上。
该 hook 接受两个参数
timeoutMs 字段,该字段表明两个变量的同步时最长的滞后时间还是用 useTransition 该 hook 的例子来举例,使用方式如下:
import { useState, useDeferredValue } from 'react';
export function FilterList({ names }) {
// 用户输入的高亮内容
const [query, setQuery] = useState('');
// 使用 useDeferredValue 创建与 query 关联的新变量,并设置最长 1s 的同步时长
const deferredValue = useDeferredValue(query, { timeoutMs: 1000 });
// 输入框变动
const changeHandler = ({ target: { value } }) => {
// 保存用户输入的值
setQuery(value);
};
return (
<div>
<input onChange={changeHandler} value={query} type="text" />
{names.map((name, i) => (
<ListItem key={i} name={name} highlight={deferredValue} />
))}
</div>
);
}
通过上面的代码中我们可以看到,我们对 input 组件进行了受控,使用 query 变量来控制其值的显示,然后我们使用 useDeferredValue 创建了一个新的变量与 query 变量进行了关联并设置了最长 1000ms 的同步时间。那么 React 将会在合适的时间内将最新的 query 值同步到 deferredValue 上来。
通过对 useTransition 和 useDeferredValue 的介绍,我们可以发现这两个新的 hook 都是用来解决在重数据处理场景下非重要变量如何延后同步的问题,通过这两个新 hook 的使用,在部分场景下可以提高我们产品的性能。
当然,由于目前 React@18 还是处于测试阶段并未完全发布,关于这两个 hook 的使用可能还会存在变动的可能性,此文章只是用于介绍这两个新的 hook。
关于更多 Concurrent Mode 相关的文档,请参考官方文档 Concurrent Mode API Reference (Experimental) – React (reactjs.org)