共计 3197 个字符,预计需要花费 8 分钟才能阅读完成。
React18 引入了一个要害概念 并发性(Concurrency)
。并发则波及到多个 更新操作
的同时执行,这能够说是 React18 中最重要的性能。
除了并发,React18 新增了两个 hook,也就是 useTransition
和useDeferredValue
。它们的作用都是升高 更新操作
的优先级,但问题是,何时应该应用它们?
并发(Concurrent)
在实现 ” 并发 ” 之前,渲染是同步的 ( 所谓的同步,就是指如果 react 的某个组件执行工夫长,它无奈中断,会始终执行,直到组件齐全渲染到 DOM 中。在这个过程中,因为 Javascript 是单线程的,因而渲染工作会占满 JavaScript 线程,阻塞浏览器的主线程,从而导致用户无奈进行交互操作)。
然而,有了并发渲染(并发指的就是通过 time slice 将工作拆分为多个,而后 react 依据优先级来实现调度策略,将低优先级的工作先挂起,将高优先级的任务分配到浏览器主线程的一帧的闲暇工夫中去执行,如果浏览器在以后一帧中还有残余的闲暇工夫,那么 React 就会利用闲暇工夫来执行剩下的低优先级的工作 ),react 的渲染和更新能够被中断和复原。那么如果在执行某个组件更新过程中又有了新的更新申请达到。比方咱们上面的input 输出事件
,那么 React 就会创立一个 新的更新版本
。这种状况下,在某个时间段内可能会同时存在多个 更新版本
。
为了优化上述问题,React 18 提供了新的 Hook 函数 useTransition
,它能够将 多个版本的更新
打包到一起,在将来的某一帧闲暇工夫内执行,从而优化利用的性能和响应工夫。而useDeferredValue
的作用是将某个值的更新推延到将来的某个工夫片内执行,从而防止不必要的反复渲染和性能开销。
没有应用任何优化伎俩,同步更新
假如咱们有一个蕴含从 0 到 19,999 数字的数组。这些数字在用户界面上显示为一个列表。该用户界面还有一个文本框,容许咱们过滤这些数字。例如,我能够通过在文本框中输出数字 99 来过滤掉以 99 结尾的数字。
import {useState, useTransition} from "react";
const numbers = [...new Array(20000).keys()];
export default function App() {const [query, setQuery] = useState("");
const handleChange = (e) => {setQuery(e.target.value);
};
return (
<div>
<input type="number" onChange={handleChange}/>
<div>
{numbers.map((i, index) => (
query
? i.toString().startsWith(query)
&& <p key={index}>{i}</p>
: <p key={index}>{i}</p>
))
}
</div>
</div>
);
}
因为数组中有 20,000 个元素,过滤将是一个有点耗时的过程。当咱们试图在文本框中输出一个数字时,咱们能够察看到这一点。输出的数值呈现在文本框中会有一个滞后,因为每一个按键之后的渲染都会破费一些工夫。
useTransition
接下来咱们应用 useTransition
来批改一下下面的代码
function App() {const [query, setQuery] = useState("");
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {startTransition(() => {setQuery(e.target.value);
});
};
const list = useMemo(() => (numbers.map((i, index) => (
query
? i.toString().startsWith(query)
&& <p key={index}>{i}</p>
: <p key={index}>{i}</p>
))
), [query]);
return (
<div>
<input type="number" onChange={handleChange} />
<div>
{
isPending
? "Loading..."
: list
}
</div>
</div>
);
}
从下面能够看到 useTransation
返回一个蕴含两个子项的数组。
isPending
: 通知你目前是否有一些 更新操作
任然在期待中(尚未被 React 执行,并以较低的优先级解决)startTransition
: React 会以一个较低的优先级调度被它包装的 更新操作
。
这样,就确保了用户和输入框的交互操作放弃晦涩。而后再通过 isPending
来判断是否能够更新 UI。
useDeferredValue
useDeferredValue
的作用和 useTransition
统一,都是用于在不阻塞 UI 的状况下更新状态。然而应用场景不同。
useTransition
是让你可能齐全管制哪个 更新操作
应该以一个比拟低的优先级被调度。然而,在某些状况下,可能无法访问理论的 更新操作
(例如,状态是从父组件上传下来的)。这时候,就能够应用useDeferredValue
来代替。
用 React 团队成员 Dan 的话说 useDeferredValue
次要是:
useful when the value comes“from above”and you don’t actually have control over the corresponding setState call.
它的意思就是: 当值来自 “ 下层 ”,而你实际上不能管制相应的 setState 调用时,这个办法很有用。
这就比拟符合咱们下面所举例子的场景。
那咱们就须要将下面的例子改成如下:
import {useState, useMemo, useDeferredValue} from "react";
const numbers = [...new Array(200000).keys()];
export default function App() {const [query, setQuery] = useState("");
const handleChange = (e) => {setQuery(e.target.value);
};
return (
<div>
<input type="number" onChange={handleChange} value={query} />
<List query={query} />
</div>
);
}
function List(props) {const { query} = props;
const defQuery = useDeferredValue(query);
const list = useMemo(() => (numbers.map((i, index) => (
defQuery
? i.toString().startsWith(defQuery)
&& <p key={index}>{i}</p>
: <p key={index}>{i}</p>
))
), [defQuery]);
return (
<div>
{list}
</div>
);
}
总结
如上所述,useTransition
间接管制 更新状态的代码
,而useDeferredValue
管制一个受状态变动影响的值。它们做的是同样的事, 帮忙进步用户体验(UX),不应该同时应用这两者。
相同,如果你能够拜访 更新操作
,并且有一些 更新操作
应该以较低的优先级解决,就应用useTransition
。如果你没有这种权限,就应用useDeferredValue
。
参考
useTransition and useDeferredValue in React 18
UseTransition() Vs UseDeferredValue() In React 18