翻译: 卷帘依旧
在Web应用中,UI更新(比如在输入框中输入字段,从下拉框选择一个值)的优先级应该高一些,而其他操作(比如显示列表过滤内容)优先级要低一些。
直到现在,React也并未提供提高UI更新优先级的工具。
幸运的事,从React 18(2021.06发布了alpha版本)开始,我们就可以开启并发模式-concurrent,concurrent模式允许将UI更新标记为高优先级的或者可中断的低优先级操作。
在这篇文章中,你会掌握如何使用useTransition()将UI更新标记为低优先级,这种操作对大量的非紧急更新非常有用。
1. useTransition()钩子
默认情况下,我们认为React中的所有更新都是紧急的(也就是所有更新的优先级相同)。那会导致一个问题-快速更新会被大量更新拖慢速度。
然而,从React 18中新增特性-concurrency之后,你就可以将某些更新标记为可中断的和非紧急的-也就是所谓的transitions。这种新特性在大量的UI更新操作中尤其有效,比如过滤一个较大的列表。
useTransition()这个钩子函数使得用户能够在React组件中使用concurrent模式特性。
调用const [isPending, startTransition] = useTransitionHook()返回一个具有两个成员的数组:
isPending: 指明这个transition正在加载中(pending)startTransition(回调): 允许用户将回调中的任何UI更新标记为transitions.
import { useTransition } from 'react';
function MyComponent() {
const [isPending, startTransition] = useTransition();
// ...
const someEventHandler = (event) => {
startTransition(() => {
// Mark updates as transitions
setValue(event.target.value);
});
}
return <HeavyComponent value={value} />;
}
为了能够使用useTransition()钩子,请确保开启concurrent模式
2. 紧急的大量UI更新
接下来考虑一个所有更新都很紧急的例子,以及这些大量更新是如何影响用户体验的。
比如有一个员工名字列表和一个查找员工的姓名搜索框,这个组件会高亮显示匹配搜索内容的员工姓名。
以下是可能实现的代码:
import { useState } from 'react';
export function FilterList({ names }) {
const [query, setQuery] = useState('');
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={query} />
))}
</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>
);
}
<FilterList names={names} />接受一个大的姓名列表。在组件内部,query是包含查询字符串的状态变量。输入框是一个控制组件-用于在用户输入改变时更新query状态变量。
打开示例页面并快速在输入框内键入查询字段,你可能会注意到键入延迟以及用户界面在明显的时间内没有响应。
为什么会出现这种现象,如何解决呢?
在用户键入时更新输入框的值是一个必须快速执行的紧急任务,更新高亮显示匹配列表是一个繁重且不紧急的任务。
大量的非紧急任务落后于轻量的紧急任务。
useTransition()钩子能够帮助你区分紧急的UI更新和非紧急的UI更新。
3. 大量的UI更新作为过渡(transitions)
之前已经提高了,你可以使用useTransition()告诉React哪些UI更新是紧急的(比如更新输入框的值)和哪些UI更新是不紧急的transitions(比如更新高亮匹配查询内容的姓名列表)
我们来对FilterList组件做一下必要的调整。
首先,我们调用[isPending, startTransition] = useTransition()钩子来访问startTransition()函数,然后专门这个transition创建了一个保存状态的状态变量。
import React, { useState, useTransition } from "react";
export function FilterList({ names }) {
const [query, setQuery] = useState("");
const [highlight, setHighlight] = useState("");
const [isPending, startTransition] = useTransition();
const changeHandler = ({ target: { value } }) => {
setQuery(value);
startTransition(() => setHighlight(value));
};
return (
<div>
<input onChange={changeHandler} value={query} type="text" />
{isPending ? 'pending' : (
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>
);
}
打开这个使用了transitionos特性的示例,如果你非常快速地在输入框中键入,你会注意到高亮列表的延迟更新。
React将紧急任务(当用户键入时更新输入框)的更新和非紧急任务(高亮显示过滤内容)的渲染区分开了,这样的操作提升了用户体验。
4. 总结
React中的并发模式(concurrent mode)将紧急任务和非紧急任务区分开,使UI更新更加人性化。
在开启React 18并发模式新特性之后,你可以使用useTransition()钩子进而使用startTransition(callback)函数。
useTransition()使你能够将默写更新标记为过渡(transitions):
const [isPending, startTransition] = useTransition();
startTransition(() => {
// Mark updates as transitions
setStateValue(newValue);
});
P.S.: 除了使用useTranstion(),使用React 18的另一个新特性-userDeferredValue()是不是能够除掉<FilterList>组件中重复的highlight状态变量呢?在评论区写下你的想法吧😊