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

React StrictMode导致数组数据重复翻倍问题咨询

问题描述

我知道React StrictMode会导致日志重复输出,但从没遇到过它改变数组或变量值的情况。关闭StrictMode时,data状态正常包含25条数据;开启后,data变成50条,是前25条的重复。

自定义Hook代码

const useRequest = (page) => {
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState([])
  const [error, setError] = useState('')

  useEffect(() => {
      const url = `someURL/{page}/25` <-- 这里是要获取的条目数
      setLoading(true)
      setError('')

      fetch(url)
      .then(res => res.json())
      .then(data => setData(prev => [...prev, ...data.items])
      })

      setLoading(false)

  },[page])

  return { loading, data, error}
}

App.js代码

const App = () => {
  const [pageNumber,setPageNumber] = useState(1)
  const { loading, data, error } = useRequest(pageNumber)

  console.log(data)
  return (
    <div className="App">
      <main>
        {data.map((user,id) => 
          <Card 
            key={id} 
            userData={user}
          />
        )}
      </main>
    </div>
  );
}
问题原因

React StrictMode在开发环境下会重复执行副作用函数(比如useEffect),目的是帮你检测潜在的代码问题。你的代码里存在两个关键问题:

  1. 没有给异步请求添加清理逻辑,导致StrictMode触发的两次useEffect都发起了page=1的请求,两次请求完成后都执行了setData(prev => [...prev, ...data.items]),第一次把数组变成25条,第二次又追加25条相同数据,最终得到50条重复内容;
  2. setLoading(false)放在fetch外部同步执行,不管请求是否完成都会立刻把loading设为false,属于逻辑错误。
修复方案

修改自定义Hook,添加请求取消逻辑并修正loading状态的更新时机:

const useRequest = (page) => {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState([]);
  const [error, setError] = useState('');

  useEffect(() => {
    // 创建AbortController用于取消请求
    const abortController = new AbortController();
    const signal = abortController.signal;

    const fetchData = async () => {
      setLoading(true);
      setError('');
      try {
        // 修正模板字符串的变量插入错误
        const url = `someURL/${page}/25`;
        const res = await fetch(url, { signal });
        const responseData = await res.json();
        setData(prev => [...prev, ...responseData.items]);
      } catch (err) {
        // 忽略取消请求的错误
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        // 请求完成或失败后再更新loading状态
        setLoading(false);
      }
    };

    fetchData();

    // 清理函数:组件卸载或page变化时取消未完成的请求
    return () => abortController.abort();
  }, [page]);

  return { loading, data, error };
};

额外说明

  • 模板字符串里的变量需要用${变量名}包裹,你之前写的{page}是错误的,会导致URL不正确;
  • 使用AbortController后,当StrictMode第二次执行useEffect时,会先触发第一次useEffect的清理函数,取消第一次的请求,这样只有第二次的请求会完成,避免重复追加数据;
  • 如果你的场景是切换分页(而非加载更多),可以把setData(prev => [...prev, ...responseData.items])改成setData(responseData.items),每次切换page直接替换数据,不会有重复问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 13:17:55