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

React中如何更新数组类型state?重复数据问题排查与解决

API数据重复追加到数组的问题排查与解决

问题描述

我有两个返回JSON数据的API,在state中定义了空数组allNews,期望在useEffect调用时将两个API返回的数据追加到该数组中。但最终allNews数组出现重复数据,本该是[Array(20), Array(20)],实际却为[Array(20), Array(20), Array(20), Array(20)],后两项与前两项内容重复。

错误原因

  1. React Strict Mode的开发环境行为:开发环境下,React Strict Mode会重复执行副作用函数(useEffect)、组件函数及dispatch操作,用于检测代码中的副作用不一致问题。你的useEffect使用空依赖数组,会触发两次执行,导致循环两次调用getAllNews,每个API被请求两次,最终触发四次dispatch。
  2. 副作用缺乏幂等性:没有判断数据是否已加载,每次useEffect执行都会重新发起请求并追加数据,导致重复内容被加入数组。

解决方法

方案一:添加加载状态保证幂等性

通过新增状态标记数据是否已加载,避免重复执行请求:

修改AppProvider代码:

const AppContext = createContext()
const urls = ['api/load-all-news/0/20', 'api/load-all-news/20/20']

const initialState = {
    allNews: [],
}

const AppProvider=({children})=>{
    const [state, dispatch] = useReducer(reducer, initialState);
    const [isNewsLoaded, setIsNewsLoaded] = useState(false); // 新增加载状态

    const getAllNews = async(url)=>{
      try{
        const res = await fetch(url)
        const data = await res.json()
        
        dispatch({ type: "ALL_NEWS", payload: data.news_list})
      }catch(err){
        console.log(err)
      }
    }
    
    useEffect(()=>{
        if(!isNewsLoaded){
            urls.forEach(url => getAllNews(url));
            setIsNewsLoaded(true); // 标记为已加载
        }
     },[isNewsLoaded])
  
    return<AppContext.Provider value={{...state}}>{children}</AppContext.Provider>
}

const useGlobalContext=()=>{
    return useContext(AppContext)
}

export {AppContext, AppProvider, useGlobalContext}

方案二:批量请求合并dispatch

使用Promise.all同时发起两个API请求,一次性将所有数据追加到数组,减少dispatch次数,同时通过数组长度判断避免重复请求:

1. 修改AppProvider代码

const AppContext = createContext()
const urls = ['api/load-all-news/0/20', 'api/load-all-news/20/20']

const initialState = {
    allNews: [],
}

const AppProvider=({children})=>{
    const [state, dispatch] = useReducer(reducer, initialState);

    const getAllNews = async()=>{
      try{
        // 同时请求两个API
        const responsePromises = urls.map(url => fetch(url));
        const responses = await Promise.all(responsePromises);
        // 解析所有响应数据
        const dataList = await Promise.all(responses.map(res => res.json()));
        // 收集所有新闻列表
        const newsData = dataList.map(item => item.news_list);
        dispatch({ type: "ALL_NEWS", payload: newsData});
      }catch(err){
        console.log(err)
      }
    }
    
    useEffect(()=>{
        // 仅当数组为空时发起请求
        if(state.allNews.length === 0){
            getAllNews();
        }
     },[state.allNews.length])
  
    return<AppContext.Provider value={{...state}}>{children}</AppContext.Provider>
}

const useGlobalContext=()=>{
    return useContext(AppContext)
}

export {AppContext, AppProvider, useGlobalContext}

2. 对应修改Reducer代码

因为现在payload是包含两个数组的集合,需要展开追加到allNews:

const Reducer = (state, action) => {
  if (action.type === "ALL_NEWS") {
    return {
      ...state,
     allNews: [...state.allNews, ...action.payload] // 展开payload中的数组元素
    };
  }
    return state;
}

export default Reducer;

注意:React Strict Mode的重复执行仅存在于开发环境,生产环境不会有此行为。上述方案既解决了开发环境的重复问题,也保证了代码的健壮性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 14:29:58