你构建了一个小型的 React 仪表板,然后发现在调整窗口大小或快速滚动时浏览器会卡顿。图表卡顿,按钮冻结,用户感到很烦躁。我曾经遇到过同样的问题——每次发生微小的调整大小事件时,图表都会重新渲染,而使用较慢机器的用户感觉就像在拖着沉重的负担。
修复方法虽小却有效:节流。我们限制了耗时任务的运行频率,应用又恢复了流畅。在这篇文章中,我将用通俗易懂的语言解释节流的重要性、其工作原理以及如何在 React 中使用它。
🧩 节流为何重要(“如何”之前先说“为什么”)
把节流想象成繁忙人行横道上的交警。没有交警,每个人都会冲过去,造成混乱。有了交警,每隔几秒钟就会有一个人过马路——井然有序,可预测。
在 Web 应用中,诸如scroll、resize、mousemove和 之类的事件input每秒可能会触发数十次甚至数百次。如果您的处理程序执行繁重的工作(重新渲染、计算布局、调用 API),则在每个事件上运行它将会:
- 降低浏览器速度(CPU 峰值),
- 导致许多不必要的网络调用,并且
- 使 UI 感觉不稳定。
节流功能让你能够控制:每 N 毫秒最多运行一次处理程序。这减少了工作量并保持 UI 的响应速度。
🧠 Throttle 与 Debounce 的简单类比
- 限流: 保安每 N 秒放行一个人。即使 100 个人等待,也总有一个人能按时通过。 适用于:
scroll、、resize周期性分析。 - 防抖动: 计时器会等待静音,然后让最后一个人通过。如果继续有人来,它会继续等待。 适用于: 边输入边搜索,需要等到输入停止后再进行。
两者都很有用——根据问题进行选择。
🛠️ 简单的节流实现(纯 JS)
让我们构建一个小的、易于阅读的节流阀功能。
// throttle.js
function throttle(fn, wait = 200) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= wait) {
lastCall = now;
fn.apply(this, args);
}
};
}
export default throttle;
🔍 为什么这样做有效(一步一步)
lastCall存储fn上次运行的时间。- 对于每个事件,我们都会检查当前时间(
now)。 - 如果
now - lastCall >= wait,我们运行fn并更新lastCall。 - 否则,我们将忽略此事件。
这是一个前沿节流阀——它立即运行,然后阻塞直到waitms 通过。
📈 React 用例:节流窗口调整大小
假设你想在窗口调整大小时更新图表布局。当用户拖动窗口角落时,调整大小会持续触发——我们最多每 200 毫秒更新一次即可。
// useWindowSizeThrottled.jsx
import { useEffect, useState } from "react";
import throttle from "./throttle";
export default function useWindowSizeThrottled(wait = 200) {
const isClient = typeof window !== "undefined";
const [size, setSize] = useState({
width: isClient ? window.innerWidth : 0,
height: isClient ? window.innerHeight : 0,
});
useEffect(() => {
const handleResize = throttle(() => {
setSize({ width: window.innerWidth, height: window.innerHeight });
}, wait);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, [wait]);
return size;
}
✅ 逐步分解
useState保持当前窗口大小。- 在里面
useEffect,我们handleResize使用throttle助手创建一个节流装置。 - 我们加入
handleResize到resize活动中。 - 在清理时,我们删除监听器以避免泄漏。
这可以防止setSize每秒被调用数十次,从而减少重新渲染和 CPU 使用率。
⚙️ 改进 React 的节流阀(useRef 方法)
如果您的组件经常重新渲染,重新创建节流阀可能会导致令人惊讶的行为。useRef保持稳定状态而不导致重新渲染:
import { useEffect, useRef, useState } from "react";
function useWindowSizeThrottledRef(wait = 200) {
const isClient = typeof window !== "undefined";
const [size, setSize] = useState({
width: isClient ? window.innerWidth : 0,
height: isClient ? window.innerHeight : 0,
});
const lastCallRef = useRef(0);
useEffect(() => {
function handler() {
const now = Date.now();
if (now - lastCallRef.current >= wait) {
lastCallRef.current = now;
setSize({ width: window.innerWidth, height: window.innerHeight });
}
}
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, [wait]);
return size;
}
✨ 为什么useRef有帮助
lastCallRef在渲染过程中持续存在,无需触发重新渲染。- 处理程序的内部状态在整个组件生命周期内保持稳定。
⚠️ 常见错误和误解
- 混淆节流阀和去抖动——根据需要选择。
- 不清理监听器——总是在清理时删除事件监听器。
- 每次渲染时重新创建节流阀——会导致丢失内部状态;使用
useRef或useCallback。 - 盲目限制 API 调用——如果用户期望即时反馈(搜索),去抖动可能会更好。
- 假设油门使事情变得“实时” ——油门限制频率,但如果
wait很大的话,可能会延迟响应。
🧾 真实用例
scroll用于粘性标头或分析的节流处理程序。- 限制
resize布局重新计算的事件。 - 限制遥测或分析以避免服务器端限制。
- 网络游戏中限制速率的光标移动处理程序。
- 通过频繁流或 websockets 控制 UI 更新。
🎯 福利:如何在面试中解释节流
当面试官问“什么是节流?”时,请简短回答并给出一个简单的例子。
🗣️ 简短回答(30秒)
节流限制了函数运行的频率(每个给定间隔最多一次),这可以防止昂贵的处理程序运行过于频繁并提高 UI 性能。
💻 快速示例
绘制或粘贴简单的throttle(fn, wait)代码并解释lastCall+Date.now()逻辑。
🔁 常见后续问题及解答
- 问: 节流阀和防抖动有什么区别? 答: 节流阀强制保持稳定的速度;防抖动则等待静音。
- 问: 领先油门和落后油门哪个好? 答: 领先油门会立即启动,然后等待;落后油门会在爆发结束时启动。您可以为两者设置选项。
- 问: 极端情况? 答: 时间漂移、渲染过程中的处理程序身份以及突发事件后缺少最终调用——准备好讨论解决方案。
🧩 迷你白板挑战
设计可能包括和的throttle(fn, wait, options)位置。绘制一个时间线,显示事件爆发期间何时运行。options``leading``trailing``fn
🧠 关键要点
- 节流= 每 N毫秒最多运行一次- 非常适合周期性限制。
- 去抖动= 事件停止后运行 - 非常适合等待用户完成输入。
- 在 React 中,保持处理程序稳定(
useRef,useCallback)并始终删除监听器。 wait通过在真实设备上进行测试来选择 ——100-300ms是 UI 工作的常见范围。- 记住用户的期望——限制会减少 CPU/网络,但可能会延迟响应。
👋 关于我
大家好,我是 Saurav Kumar — 一名软件工程师,热衷于使用 JavaScript、TypeScript、React、Next.js 和 React Native 构建现代 Web 和移动应用程序。
我正在探索人工智能工具如何加速开发,
并分享适合初学者的教程来帮助其他人更快地成长。作者www.mjsyxx.com