React中如何更新数组类型state?重复数据问题排查与解决
API数据重复追加到数组的问题排查与解决
问题描述
我有两个返回JSON数据的API,在state中定义了空数组allNews,期望在useEffect调用时将两个API返回的数据追加到该数组中。但最终allNews数组出现重复数据,本该是[Array(20), Array(20)],实际却为[Array(20), Array(20), Array(20), Array(20)],后两项与前两项内容重复。
错误原因
- React Strict Mode的开发环境行为:开发环境下,React Strict Mode会重复执行副作用函数(
useEffect)、组件函数及dispatch操作,用于检测代码中的副作用不一致问题。你的useEffect使用空依赖数组,会触发两次执行,导致循环两次调用getAllNews,每个API被请求两次,最终触发四次dispatch。 - 副作用缺乏幂等性:没有判断数据是否已加载,每次
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
相关产品推荐
相关产品推荐

