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
相关产品推荐
相关产品推荐

