写点什么

web 前端培训 React 如何原生实现防抖

作者:@零度
  • 2022 年 6 月 09 日
  • 本文字数:1997 字

    阅读完需:约 7 分钟

useTransition Demo

useTransition 是一个新增的原生 Hook,用于以较低优先级执行一些更新。

在我们的 Demo 中有 ctn 与 num 两个状态,其中 ctn 与输入框的内容受控。

当触发输入框 onChange 事件时,会同时触发 ctn 与 num 状态变化。其中触发 num 状态变化的方法(即 updateNum)被包裹在 startTransition 中:

function App() {

const [ctn, updateCtn] = useState('');

const [num, updateNum] = useState(0);

const [isPending, startTransition] = useTransition();

return (

<div >

<input value={ctn} onChange={({target: {value}}) => {

updateCtn(value);

startTransition(() => updateNum(num + 1))

}}/>

<BusyChild num={num}/>

</div>

);

}

num 会作为 props 传递给 BusyChild 组件。在 BusyChild 中通过 while 循环人为增加组件 render 所消耗的时间:

const BusyChild = React.memo(({num}: {num: number}) => {

const cur = performance.now();

// 增加 render 的耗时

while (performance.now() - cur < 300) {}

return <div>{num}</div>;

})

所以,在输入框输入内容时能明显感到卡顿。



按理说,onChange 中会同时触发 ctn 与 num 的状态变化,他们在视图中的显示应该是同步的。

然而实际上,输入框连续输入一段文字(即 ctn 的状态变化连续展示在视图中)后,num 才会变化一次_前端培训

如下图,初始时输入框没有内容,num 为 0:



输入框输入很长一段文字后,num 才变为 1:



这种效果就像:被 startTransition 包裹的更新都有防抖的效果一样。

这是如何实现的呢?

什么是 lane

在 React18 中有一套更新优先级机制,不同地方触发的更新拥有不同优先级。优先级的定义依据是符合用户感知的,比如:

  • 用户不希望输入框输入文字会有卡顿,所以 onChange 事件中触发的更新是同步优先级(最高优)

  • 用户可以接受请求发出到返回之间有等待时间,所以 useEffect 中触发的更新是默认优先级

那么优先级怎么表示呢?用一个 31 位的二进制,被称为 lane。

比如同步优先级和默认优先级定义如下:

const SyncLane = 0b0000000000000000000000000000001;

const DefaultLane = 0b0000000000000000000000000010000;

数值越小优先级越大,即 SyncLane < DefaultLane。

那么 React 每次更新是不是选择一个优先级,然后执行所有组件中这个优先级对应的更新呢?

不是。如果每次更新只能选择一个优先级,那灵活性就太差了。

所以实际情况是:每次更新,React 会选择一到多个 lane 组成一个批次,然后执行所有组件中包含在这个批次中的 lane 对应的更新

这种组成批次的 lane 被称为 lanes。

比如,如下代码将 SyncLane 与 DefaultLane 合成 lanes:

// 用“按位或”操作合并 lane

const lanes = SyncLane | DefaultLane;

entangle 机制

可以看到,lane 机制本质上就是各种位运算,可以设计的很灵活。

在此基础上,有一套被称为 entangle(纠缠)的机制。

entangle 指一种 lane 之间的关系,如果 laneA 与 laneB 纠缠,那么某次更新 React 选择了 laneA,则必须带上 laneB_前端视频

也就是说 laneA 与 laneB 纠缠在一块,同生共死了。

除此之外,如果 laneA 与 laneC 纠缠,此时 laneC 与 laneB 纠缠,那么 laneA 也会与 laneB 纠缠。

那么 entangle 机制与 useTransition 有什么关系呢?

被 startTransition 包裹的回调中触发的更新,优先级为 TransitionLanes 中的一个。

TransitionLanes 中包括 16 个 lane,分别是 TransitionLane1 到 TransitionLane16:



而 transition 相关 lane 会发生纠缠。

在我们的 Demo 中,每次 onChange 执行,都会创建两个更新:

onChange={({target: {value}}) => {

updateCtn(value);

startTransition(() => updateNum(num + 1))

}

其中:

  • updateCtn(value)由于在 onChange 中触发,优先级为 SyncLane

  • updateNum(num + 1)由于在 startTransition 中触发,优先级为 TransitionLanes 中的某一个

当在输入框中反复输入文字时,以上过程会反复执行,区别是:

  • SyncLane 由于是最高优先级,会被执行,所以我们会看到输入框中内容变化

  • TransitionLanes 相关 lane 优先级比 SyncLane 低,暂时不会执行,同时他们会产生纠缠

为了防止某次更新由于优先级过低,一直无法执行,React 有个过期机制:每个更新都有个过期时间,如果在过期时间内都没有执行,那么他就会过期。

过期后的更新会同步执行(也就是说他的优先级变得和 SyncLane 一样)

在我们的例子中,startTransition(() => updateNum(num + 1))会产生很多纠缠在一块的 TransitionLanes 相关 lane。

过了一段时间,其中某个 lane 过期了,于是他优先级提高到和 SyncLane 一样,立刻执行。

又由于这个 lane 与其他 TransitionLanes 相关 lane 纠缠在一起,所以他们会被一起执行。

这就表现为:在输入框一直输入内容,但是 num 在视图中显示的数字过了会儿才变化。

总结

今天我们聊了 useTransition 内部的一些实现,涉及到:

  • lane 模型

  • entangle 机制

  • 更新过期机制

最有意思的是,由于不同电脑性能不同,浏览器帧率会变动,所以在不同电脑中 React 会动态调节防抖的效果。

这就相当于不需要你手动设置 debounce 的时间参数,React 会根据电脑性能动态调整。

文章来源于 SegmentFault 思否

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

IT培训 www.atguigu.com

评论

发布
暂无评论
web前端培训React如何原生实现防抖_前端开发_@零度_InfoQ写作社区