大家好,我卡颂。
作为前端,想必你对防抖(debounce)、节流(throttle)这两个概念不陌生。
在react18中,基于新的并发特性,React原生实现了防抖的功能。
今天我们来聊聊这是如何实现的。
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包裹的更新都有防抖的效果一样。
这是如何实现的呢?
在React18中有一套更新优先级机制,不同地方触发的更新拥有不同优先级。优先级的定义依据是符合用户感知的,比如:
那么优先级怎么表示呢?用一个31位的二进制,被称为lane。
比如同步优先级和默认优先级定义如下:
const SyncLane = 0b0000000000000000000000000000001;
const DefaultLane = 0b0000000000000000000000000010000;
数值越小优先级越大,即SyncLane < DefaultLane。
那么React每次更新是不是选择一个优先级,然后执行所有组件中这个优先级对应的更新呢?
不是。如果每次更新只能选择一个优先级,那灵活性就太差了。
所以实际情况是:每次更新,React会选择一到多个lane组成一个批次,然后执行所有组件中包含在这个批次中的lane对应的更新
这种组成批次的lane被称为lanes。
比如,如下代码将SyncLane与DefaultLane合成lanes:
// 用“按位或”操作合并lane
const lanes = SyncLane | DefaultLane;
可以看到,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))
}
其中:
当在输入框中反复输入文字时,以上过程会反复执行,区别是:
为了防止某次更新由于优先级过低,一直无法执行,React有个过期机制:每个更新都有个过期时间,如果在过期时间内都没有执行,那么他就会过期。
过期后的更新会同步执行(也就是说他的优先级变得和SyncLane一样)
在我们的例子中,startTransition(() => updateNum(num + 1))会产生很多纠缠在一块的TransitionLanes相关lane。
过了一段时间,其中某个lane过期了,于是他优先级提高到和SyncLane一样,立刻执行。
又由于这个lane与其他TransitionLanes相关lane纠缠在一起,所以他们会被一起执行。
这就表现为:在输入框一直输入内容,但是num在视图中显示的数字过了会儿才变化。
今天我们聊了useTransition内部的一些实现,涉及到:
最有意思的是,由于不同电脑性能不同,浏览器帧率会变动,所以在不同电脑中React会动态调节防抖的效果。
这就相当于不需要你手动设置debounce的时间参数,React会根据电脑性能动态调整。
来源: 魔术师卡颂
是在写keyup事件的时候,每次触发,都会请求后台接口,为了避免,每次请求,键盘弹起之后,隔上一段时间再去请求,所以用防抖函数,多次事件触发后、事件处理函数只执行一次
在 js 中 改变窗口大小 & 上下滚动滚动条 & 反复向输入框中输入内容 ... , 如果绑定了相应的事件 , 这些事件的触发频率非常高, 严重影响用户体验和服务器的性能 , 这种问题 在js中 就叫 js 的抖动 .
防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。(信我,你看完肯定就懂了)
有些浏览器事件可以在短时间内快速触发多次,比如调整窗口大小或向下滚动页面。例如,监听页面窗口滚动事件,并且用户持续快速地向下滚动页面,那么滚动事件可能在 3 秒内触发数千次,这可能会导致一些严重的性能问题
解决当函数在某些场景下被无限制的频繁调用,会增加浏览器的负担,会造成卡顿的现象;鼠标滚动、键盘输入操作、窗口resize等等,事件持续触发时,如果间隔时间小于预设的时间,不会执行函数,同时以最后触发事件的时间为准重新计时。
应用场景:多次点击提交按钮 首次提交执行,重复提交就会等待一定的时间提交执行;首次点击提交按钮会立即执行一次debounce方法,后面3s内不触发事件才能继续执行
面试的时候我们经常会问别人是理解什么是节流和防抖,严格的可能要求你写出节流和防抖函数,这里我们抛开loadsh工具库手写节流和防抖
函数防抖的理解:连续触发事件,最后一次事件触发后,超过了规定时间还没有再次触发,这时候会执行一次事件,这就是函数的防抖
防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。节流:规定在一个单位时间内,只能触发一次函数。
防抖,顾名思义,防止抖动,以免把一次事件误认为多次,敲键盘就是一个每天都会接触到的防抖操作。想要了解一个概念,必先了解概念所应用的场景。在 JS 这个世界中,有哪些防抖的场景呢
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!