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

React Strict Mode下useEffect初始加载忽略isLoading状态问题求助

问题分析与解决方案

1. React Strict Mode 重复执行的原因

React Strict Mode在开发环境下会刻意重复执行useEffect、组件初始化等逻辑,目的是帮你提前排查潜在问题——比如副作用逻辑不健壮、内存泄漏风险、状态更新竞态等。生产环境下不会有这个重复执行的行为,所以移除Strict Mode只是临时绕开问题,长期来看还是要让代码兼容这个模式。

2. isLoading 失效的核心原因

你的useEffect依赖数组包含了isLoading,在Strict Mode的重复执行逻辑里:

  • 第一次执行useEffect时,调用异步函数并设置isLoading=true,但React的状态更新是异步批量处理的,状态还没来得及同步;
  • 第二次执行useEffect时,isLoading还是初始的false,导致加载逻辑被重复触发。
    另外,addMoreData里直接使用loadedData状态,没有用函数式更新的prev值,也会加剧闭包导致的状态不一致问题。

3. 具体修复方案

方案一:优化异步逻辑与状态更新

通过函数式更新获取最新状态,同时添加挂载标志位防止内存泄漏,让逻辑兼容Strict Mode:

// Try to load what is visible, but not more
const [loadedData, setLoadedData] = useState([]);
const [isLoading, setIsLoading] = useState(false);

// 用函数式更新获取最新的loadedData,避免闭包陷阱
const addMoreData = async () => {
  setLoadedData((prev) => {
    if (prev.length >= data.length) return prev;
    const startIndex = prev.length;
    const preferredLoadLength = 1;
    const endIndex = startIndex + preferredLoadLength;
    const newData = data.slice(startIndex, endIndex);
    return [...prev, ...newData];
  });
};

useEffect(() => {
  let mounted = true; // 标记组件是否仍挂载

  const checkAndLoad = async () => {
    const container = tableContainerRef.current;
    if (!container) return;

    const { scrollHeight, scrollTop, clientHeight } = container;
    // 提前过滤不满足条件的情况
    if (scrollHeight !== clientHeight || scrollTop !== 0 || loadedData.length >= data.length) {
      return;
    }

    if (isLoading) return;
    setIsLoading(true);
    
    await addMoreData();
    
    // 组件已卸载则不更新状态
    if (mounted) {
      setIsLoading(false);
    }
  };

  checkAndLoad();

  // 清理函数:组件卸载时标记为未挂载
  return () => {
    mounted = false;
  };
}, [data.length, loadedData.length, isLoading]);

方案二:精简依赖数组,用Ref跟踪状态

如果useEffect只需要在组件挂载和data.length变化时执行,可以用Ref跟踪最新状态,避免不必要的重复触发:

const [loadedData, setLoadedData] = useState([]);
const [isLoading, setIsLoading] = useState(false);

// 用Ref跟踪最新状态,避免闭包问题
const loadedDataLengthRef = useRef(0);
const isLoadingRef = useRef(false);

// 同步Ref与状态
useEffect(() => {
  loadedDataLengthRef.current = loadedData.length;
  isLoadingRef.current = isLoading;
}, [loadedData.length, isLoading]);

useEffect(() => {
  let mounted = true;

  const checkAndLoad = async () => {
    const container = tableContainerRef.current;
    if (!container) return;

    const { scrollHeight, scrollTop, clientHeight } = container;
    if (scrollHeight !== clientHeight || scrollTop !== 0 || loadedDataLengthRef.current >= data.length) {
      return;
    }

    if (isLoadingRef.current) return;
    isLoadingRef.current = true;
    setIsLoading(true);
    
    await addMoreData();
    
    if (mounted) {
      isLoadingRef.current = false;
      setIsLoading(false);
    }
  };

  checkAndLoad();

  return () => {
    mounted = false;
  };
}, [data.length]);

4. 保留Strict Mode的价值

Strict Mode不会影响生产环境,它能帮你:

  • 检测不安全的生命周期用法
  • 排查过时的Ref使用方式
  • 发现意外的副作用逻辑
  • 警告废弃API的调用
  • 提前暴露组件的竞态问题

所以尽量不要移除它,让代码兼容这个模式能大幅提升代码健壮性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 05:37:12