在 React 中使用 Throttle 和 Debounce
概述
Throttle和Debounce解决优化问题。
Throttle- 以特定频率跳过函数调用。
Debounce- 延迟函数调用,直到自上次调用以来经过一定时间。
Throttle & Debounce 方案:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--taNFmG_z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/e7q3kw8dwjsbcv4dux07.png)
油门使用示例:
1)如果用户调整了浏览器窗口的大小,我们需要改变网站的内容。
如果没有优化,会发生以下情况。在每个窗口调整大小事件上,都会调用窗口调整大小事件处理程序。因此,例如,如果用户在 10 秒内调整窗口大小,则可能会发生 100、200 等等。我们需要处理的事件。
Throttle允许我们设置一个时间间隔,超过该时间间隔将不会调用事件处理程序。如果我们使用Throttle指定 1 秒的间隔,那么窗口调整大小事件处理程序的执行次数将为 10。
- 向用户显示页面滚动的百分比。当用户滚动页面时,会发生
scroll事件,我们需要对其进行处理。使用throttle,我们可以通过设置时间间隔来减少用户处理的页面滚动事件的数量。
去抖示例:
1)处理用户的搜索查询数据。
当用户输入搜索查询时,会为他们提供搜索选项。它以下列方式发生。
当用户输入的文本发生变化时,会向服务器发送一个请求,我们将在其中传输已打印的字符。然后我们从服务器获得可能的搜索查询选项的响应并将它们显示给用户。
每次用户更改文本时,都会调用一个事件处理程序,在该处理程序中向服务器发出请求。
为了优化发送到服务器的请求数,我们使用Debounce。
当用户更改文本时,应用Debounce允许我们创建一个计时器,例如 1 秒。如果 1 秒过去并且用户没有再次更改文本,则调用事件处理程序并向服务器发出请求。如果用户在 1 秒内第二次更改文本,则重置第一个计时器并在 1 秒内再次创建一个新计时器。
因此,如果用户快速编辑搜索文本(不到 1 秒),那么在用户停止输入后,请求将只发送到服务器一次。
- 将分析数据发送到服务器。例如,用户在站点周围移动鼠标,我们将鼠标坐标写入一个数组,之后
Debounce允许我们仅在客户端停止移动鼠标后,才向服务器发送有关客户端鼠标移动的信息。
因此,在本文中,我将向您展示如何在 React 应用程序中使用Throttle和Debounce。
第 1 步 - 申请模板
使用create-react-app创建一个应用程序模板并运行它:
npx create-react-app throttle-debounce
cd throttle-debounce
npm start
进入全屏模式 退出全屏模式
我们用我们的样式替换文件App.css的内容:
body {
display: flex;
justify-content: center;
width: 100%;
}
h1 {
text-align: center;
margin: 0.5rem 0;
}
.l-scroll {
overflow-y: scroll;
overflow-x: hidden;
width: 380px;
height: 200px;
margin-top: 0.5rem;
}
.scroll-content {
width: 100%;
background-color: bisque;
padding: 0 1rem;
}
.l-scroll::-webkit-scrollbar {
width: 10px;
height: 8px;
background-color: darkturquoise;
}
.l-scroll::-webkit-scrollbar-thumb {
background-color: blueviolet;
}
进入全屏模式 退出全屏模式
让我们用我们的应用程序模板替换文件App.js的内容:
import './App.css';
import { useMemo } from 'react';
function App() {
return (
<>
<h1>Throttle & Debounce</h1>
<div className="l-scroll">
<div className="scroll-content">
<TallContent />
</div>
</div>
</>
);
}
// Прокручиваемый контент большой высоты
function TallContent(){
const dataElements = useMemo(() => {
const genData = [];
for(let i=1; i<=200; i++){
genData.push(
<div key={i}>Line: {i}</div>
);
}
return genData;
}, []);
return(
<>
{dataElements}
</>
);
}
export default App;
进入全屏模式 退出全屏模式
应用程序模板已经准备好,让我们继续第二步——通常的滚动事件处理程序。
第 2 步 - 正常事件处理程序
在这里,我们将为scroll个事件添加一个常规事件处理程序,并计算用户滚动页面元素时对该处理程序的调用次数。
让我们将事件处理程序的导出数量的状态添加到App个组件:
// В начале файла
import { useState, useMemo } from 'react';
// Внутри компонента App
const [scrollHandleCount, setScrollHandleCount] = useState(0);
进入全屏模式 退出全屏模式
然后我们添加一个滚动事件处理程序,为此我们将属性onScroll添加到h1标题下的元素:
// Было
<div className="l-scroll">
...
</div>
// Стало
<div className="l-scroll" onScroll={handleScroll}>
...
</div>
进入全屏模式 退出全屏模式
我们还添加一个函数来处理组件App中的事件handleScroll:
function handleScroll(){
handleUsualScroll();
}
进入全屏模式 退出全屏模式
在函数handleScroll中,我们放置了一个函数,用于处理常规事件。让我们将此函数添加到我们的App组件中:
function handleUsualScroll(){
setScrollHandleCount((prevState) => {
return ++prevState;
});
}
进入全屏模式 退出全屏模式
它只是向用户显示计数器的状态,为此我们在标题h1下添加一行代码:
<span>
Usual scroll handle count: {scrollHandleCount}
</span>
<br />
进入全屏模式 退出全屏模式
现在,当滚动页面上的元素时,我们应该看到函数调用的数量为handleUsualScroll()。
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--bbWU6bVf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/uzl1ynovnmke8uk8bc85.gif)
目前完整的组件代码App:
function App() {
const [scrollHandleCount, setScrollHandleCount] = useState(0);
return (
<>
<h1>Throttle & Debounce</h1>
<span>
Usual scroll handle count: {scrollHandleCount}
</span>
<br />
<div className="l-scroll" onScroll={handleScroll}>
<div className="scroll-content">
<TallContent />
</div>
</div>
</>
);
function handleScroll(){
handleUsualScroll();
}
function handleUsualScroll(){
setScrollHandleCount((prevState) => {
return ++prevState;
});
}
}
进入全屏模式 退出全屏模式
第 3 步 - 节流事件处理程序
Throttle在我们的例子中,事件处理程序应该调用计数器scrollThrottleHandleCount的增量,而 skip 调用以在一定间隔内递增计数器。
为了实现我们的计划,我们需要一个计时器,开始时状态Throlle变为In progress。此外,如果状态为In Progerss,则跳过用户事件(滚动页面元素)的处理。
一旦计时器触发,状态Throttle就会变为Not in progress,这意味着我们的处理程序将再次处理用户事件。因此,以指定的时间间隔跳过用户事件。
我们实现上述:
// Добавим useRef для хранения состояния inProgress
import { useState, useRef, useMemo } from 'react';
进入全屏模式 退出全屏模式
接下来,在组件App中,将事件处理程序调用计数器的状态添加为Throttle和ref以存储状态inProgress:
// Кол-во вызовов обработчика событий с Throttle
const [
scrollThrottleHandleCount,
setScrollThrottleHandleCount
] = useState(0);
// Храним состояние in progress
const throttleInProgress = useRef();
进入全屏模式 退出全屏模式
这里需要注意的是,throttleInProgress是与定时器相关联的副作用的一部分,这意味着我们会将状态存储在ref对象中,因为useRef返回的对象存在于组件的整个生命周期中,而当current对象的属性发生变化时,不会对组件进行不必要的渲染,返回useRef,而不是useState。
现在让我们将事件处理程序本身从Throttle添加到组件App:
function handleThrottleScroll(){
// Если состояние inProgress - выходим из функции,
// пропускаем обработку события
if(throttleInProgress.current){ return; }
// Устанавливаем inProgress в true и запускаем таймер
throttleInProgress.current = true;
setTimeout(() => {
// Увеличиваем состояние throttleHandleCount
// на единицу
setScrollThrottleHandleCount((prevState) => {
return ++prevState;
});
// inProgress устанавливаем в false,
// значит при следующем запуске
// setTimeout снова сработает
throttleInProgress.current = false;
}, 500);
}
进入全屏模式 退出全屏模式
剩下两个简单的步骤:将计数器状态显示从Throttle添加到用户并将handleThrottleScroll()添加到handleScroll():
// После заголовка h1
<span>
Throttle scroll handle count: {scrollThrottleHandleCount}
</span>
// В функцию handleScroll() после handleUsualScroll();
handleThrottleScroll();
进入全屏模式 退出全屏模式
结果,我们将得到:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--t2LcEMBW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/le10pgilz162y86gal38.gif)
正常的事件处理程序调用应用程序的业务逻辑 181 次,而自Throttle以来只有 9 次。
完整的组件代码App来自Throttle:
function App() {
const [scrollHandleCount, setScrollHandleCount] = useState(0);
const [
scrollThrottleHandleCount,
setScrollThrottleHandleCount
] = useState(0);
const throttleInProgress = useRef();
return (
<>
<h1>Throttle & Debounce</h1>
<span>
Usual scroll handle count: {scrollHandleCount}
</span>
<br />
<span>
Throttle scroll handle count: {scrollThrottleHandleCount}
</span>
<br />
<div className="l-scroll" onScroll={handleScroll}>
<div className="scroll-content">
<TallContent />
</div>
</div>
</>
);
function handleScroll(){
handleUsualScroll();
handleThrottleScroll();
}
function handleUsualScroll(){
setScrollHandleCount((prevState) => {
return ++prevState;
});
}
function handleThrottleScroll(){
if(throttleInProgress.current){ return; }
throttleInProgress.current = true;
setTimeout(() => {
setScrollThrottleHandleCount((prevState) => {
return ++prevState;
});
throttleInProgress.current = false;
}, 500);
}
}
进入全屏模式 退出全屏模式
让我们继续最后一步 - 实现Debounce事件处理程序。
第 4 步 - 去抖动事件处理程序
在我们的示例中,Debounce延迟递增计数器scrollDebounceHandleCount,直到经过一定时间量**since the last call** of the event handler **_。
让我们添加从Debounce、ref到事件处理程序的调用次数的状态,以将计时器 ID 存储在App组件中:
const [
scrollDebounceHandleCount,
setScrollDebounceHandleCount
] = useState(0);
const timerDebounceRef = useRef();
进入全屏模式 退出全屏模式
然后我们向用户显示数字scrollDebounceHandleCount并将我们的方法handleDebounceScroll()添加到handleScroll():
// После h1
<span>
Debound scroll handle count: {scrollDebounceHandleCount}
</span>
// В функцию handleScroll()
handleDebounceScroll();
进入全屏模式 退出全屏模式
仍然要编写函数handleDebounceScroll本身:
function handleDebounceScroll(){
// Если ID таймена установлено - сбрасываем таймер
if(timerDebounceRef.current){
clearTimeout(timerDebounceRef.current);
}
// Запускаем таймер, возвращаемое ID таймера
// записываем в timerDebounceRef
timerDebounceRef.current = setTimeout(() => {
// Вызываем увеличение счётчика кол-ва
// выполнения бизнес логики приложения с Debounce
setScrollDebounceHandleCount((prevState) => {
return ++prevState;
});
}, 500);
}
进入全屏模式 退出全屏模式
因此,仅当用户停止滚动页面元素超过或等于 500 毫秒时,计数器才会从Debounce开始递增:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--6rqFtoD_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to- uploads.s3.amazonaws.com/uploads/articles/n94rs556vj0hzhcascux.gif)
组件App的全文:
function App() {
const [scrollHandleCount, setScrollHandleCount] = useState(0);
const [
scrollThrottleHandleCount,
setScrollThrottleHandleCount
] = useState(0);
const [
scrollDebounceHandleCount,
setScrollDebounceHandleCount
] = useState(0);
const throttleInProgress = useRef();
const timerDebounceRef = useRef();
return (
<>
<h1>Throttle & Debounce</h1>
<span>
Usual scroll handle count: {scrollHandleCount}
</span>
<br />
<span>
Throttle scroll handle count: {scrollThrottleHandleCount}
</span>
<br />
<span>
Debound scroll handle count: {scrollDebounceHandleCount}
</span>
<div className="l-scroll" onScroll={handleScroll}>
<div className="scroll-content">
<TallContent />
</div>
</div>
</>
);
function handleScroll(){
handleUsualScroll();
handleThrottleScroll();
handleDebounceScroll();
}
function handleUsualScroll(){
setScrollHandleCount((prevState) => {
return ++prevState;
});
}
function handleThrottleScroll(){
if(throttleInProgress.current){ return; }
throttleInProgress.current = true;
setTimeout(() => {
setScrollThrottleHandleCount((prevState) => {
return ++prevState;
});
throttleInProgress.current = false;
}, 500);
}
function handleDebounceScroll(){
if(timerDebounceRef.current){
clearTimeout(timerDebounceRef.current);
}
timerDebounceRef.current = setTimeout(() => {
setScrollDebounceHandleCount((prevState) => {
return ++prevState;
});
}, 500);
}
}
进入全屏模式 退出全屏模式
订阅博客,点赞,添加到书签。
让我们不要忘记独角兽。
感谢您的关注。
更多推荐

所有评论(0)