使用过渡(useTransition)初探,React 18 新功能优先级初探
翻译: 卷帘依旧
原文地址: dmitripavlutin.com/react-usetr…
在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>
);
}
在线运行transition示例
打开这个使用了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
状态变量呢?在评论区写下你的想法吧????