You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么状态未变化时自定义useInfiniteScroll钩子仍会重复渲染?

问题原因

  • 核心是你调用setIsAtBottom时没有做等值判断,即使新计算的底部状态和当前状态完全一致,也会触发状态更新流程。
  • React官方渲染逻辑明确说明:当给状态设置与当前完全相同的值时,React会跳过子组件渲染与后续副作用执行,但可能会额外重渲染一次使用该状态的当前组件,这是React内部的校验逻辑导致的,仅作用于当前组件不会向下渗透,就是你步骤3看到的异常渲染来源。
  • 后续再触发相同值的状态更新时,React会直接执行 bailout 逻辑完全跳过渲染,所以步骤4仅会打印状态日志不会打印RENDERED。

修复方案

只需在更新状态前对比新旧值,仅在值发生变化时执行状态更新即可,修改useInfiniteScroll的定时器回调逻辑:

import { useEffect, useRef, useState } from "react";

const isHitBottom = () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  return scrollTop + clientHeight >= scrollHeight;
};

export function useInfiniteScroll() {
  console.log("RENDERED");
  const ref = useRef<NodeJS.Timeout | null>(null);
  const [isAtBottom, setIsAtBottom] = useState<boolean>(false);
  const debounced = () => {
    if (ref.current) {
      clearTimeout(ref.current);
    }
    ref.current = setTimeout(() => {
      const isBottom = isHitBottom();
      // 用函数式更新拿到上一次状态做对比
      setIsAtBottom(prevState => {
        if (isBottom === prevState) return prevState;
        console.log(isBottom ? "HIT BOTTOM" : "NOT HIT BOTTOM");
        return isBottom;
      });
    }, 500);
  };

  useEffect(() => {
    window.addEventListener("scroll", debounced);
    return () => {
      window.removeEventListener("scroll", debounced);
    };
  }, [debounced]);
  return isAtBottom;
}

额外优化说明:给useEffect的依赖数组加上debounced,也可以配合useCallback包裹debounced函数,避免重复绑定滚动事件。


内容的提问来源于stack exchange,提问作者JunKim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 15:57:03