【React】节流会在react内失效??

节流在React内不会"天生失效",但如果没处理好闭包陷阱或依赖项更新,很容易出现节流不生效、重复执行的问题。

核心原因是React组件渲染会创建新的函数实例,导致节流函数被频繁重置。

"失效"常见场景

1、节流函数未缓存,每次渲染都新建实例

javascript 复制代码
// 错误示例:每次组件渲染都会创建新的 throttle 函数
const handleScroll = throttle(() => {
  console.log('滚动触发');
}, 1000);

useEffect(() => {
  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, []); // 依赖为空,但 handleScroll 每次渲染都变

组件每次重渲染(如 父组件更新、状态变化),handleScroll 都会变成新的节流函数实例;

旧的节流函数没有被正确移除,新的又被添加,导致多个节流函数同时生效,相当于"节流失效";

2、节流函数依赖外部状态,闭包导致值滞后

javascript 复制代码
const [count, setCount] = useState(0);

// 节流函数依赖 count,但闭包会捕获初始的 count 值
const handleClick = throttle(() => {
  console.log('当前 count:', count); // 永远打印 0,即使 count 已更新
}, 1000);

return <button onClick={handleClick}>点击</button>;

节流函数创建时捕获了初始 count,后续 count 更新后,节流函数仍引用旧值;

业务逻辑错误,看起来像"节流失效"(实际是值没有更新);

如何避免

1、使用 useCallback 固定节流函数的引用,避免每次渲染新建实例

javascript 复制代码
import { useCallback, useEffect } from 'react';
import { throttle } from 'lodash'; // 以 lodash.throttle 为例

const MyComponent = () => {
  // 1. 用 useCallback 缓存节流函数,依赖为空则实例永久不变
  const handleScroll = useCallback(
    throttle(() => {
      console.log('滚动触发(1秒1次)');
    }, 1000),
    [] // 无依赖,函数实例稳定
  );

  // 2. 监听滚动事件,依赖为 handleScroll(稳定引用)
  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    // 3. 卸载时移除事件 + 取消节流(避免内存泄漏)
    return () => {
      window.removeEventListener('scroll', handleScroll);
      handleScroll.cancel(); // 关键:取消未执行的节流任务
    };
  }, [handleScroll]); // 依赖稳定的 handleScroll

  return <div>监听滚动</div>;
};

2、依赖外部状态时,用 useRef 实时获取最新值(解决闭包问题)

如果节流函数需要依赖组件状态 / 属性,用 useRef 存储最新值,避免闭包滞后:

javascript 复制代码
import { useCallback, useRef, useState } from 'react';
import { throttle } from 'lodash';

const MyComponent = () => {
  const [count, setCount] = useState(0);
  // 1. 用 ref 存储最新 count(ref.current 实时更新)
  const countRef = useRef(count);
  useEffect(() => {
    countRef.current = count; // 每次 count 变化,更新 ref
  }, [count]);

  // 2. 节流函数通过 ref 获取最新 count,而非直接依赖
  const handleClick = useCallback(
    throttle(() => {
      console.log('当前 count:', countRef.current); // 实时获取最新值
    }, 1000),
    [] // 无依赖,函数实例稳定
  );

  return (
    <div>
      <p>count:{count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>加1</button>
      <button onClick={handleClick}>节流点击</button>
    </div>
  );
};

关键注意事项

  1. 必须取消节流任务:组件卸载时,除了移除事件监听,还要调用 throttle 函数的 cancel() 方法(如 handleScroll.cancel() ),避免残留的节流任务在组件卸载后执行,导致报错;
  2. 避免在节流函数内直接修改状态:节流函数执行频率低,若直接修改状态(如 setCount(prev => prev + 1) ),需确保逻辑符合预期(例如1秒内多次触发,只执行一次状态更新);
  3. 慎用"即时执行"的节流配置:部分节流库支持 leading: true(首次触发立即执行)或 trailing: false(最后一次触发不执行),需要根据业务场景选择,避免不符合预期的执行时机;

总结

节流在 React 内"失效"的本质时函数实例频繁重置或闭包捕获旧值,而非节流本身的问题。通过 useCallback 缓存节流函数 + useRef 实时获取状态,即可稳定实现节流效果。

相关推荐
阿虎儿27 分钟前
React Context 详解:从入门到性能优化
前端·vue.js·react.js
颜酱1 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
Sailing1 小时前
🚀 别再乱写 16px 了!CSS 单位体系已经进入“计算时代”,真正的响应式布局
前端·css·面试
FansUnion1 小时前
我如何用 Next.js + Supabase + Cloudflare R2 搭建壁纸销售平台——月成本接近 $0
javascript
喝水的长颈鹿1 小时前
【大白话前端 03】Web 标准与最佳实践
前端
爱泡脚的鸡腿1 小时前
Node.js 拓展
前端·后端
左夕2 小时前
分不清apply,bind,call?看这篇文章就够了
前端·javascript
Zha0Zhun3 小时前
一个使用ViewBinding封装的Dialog
前端
兆子龙3 小时前
从微信小程序 data-id 到 React 列表性能优化:少用闭包,多用 data-*
前端
滕青山3 小时前
文本行过滤/筛选 在线工具核心JS实现
前端·javascript·vue.js