React组件重复渲染引发Spring Boot API重复请求及主键约束异常问题排查
解决React useFetch钩子重复请求导致的API约束错误问题
看起来你遇到的是React开发环境下Strict Mode引发的重复请求问题,再加上依赖项引用不稳定的推波助澜,最终导致Spring Boot后端收到重复的创建请求,触发主键重复的约束异常。下面是具体的分析和解决方案:
问题根源分析
- React 18 Strict Mode的特性:在开发环境中,React的Strict Mode会自动执行两次
useEffect(以及其他生命周期函数),目的是帮助你检测潜在的副作用问题。这是开发环境的专属行为,生产环境不会出现,但对于POST这类非幂等请求来说,直接导致了重复创建实体的问题。 - 依赖项引用不稳定:你的
useEffect依赖数组中包含body参数,而body通常是一个对象。在组件每次渲染时,即使对象内容完全相同,它也会被重新创建为一个新的引用,这会让React认为依赖项发生了变化,从而再次触发useEffect,发起重复请求。
具体解决方案
1. 给useFetch添加请求取消机制(优先推荐)
使用AbortController来取消未完成的请求,当useEffect重新执行时,先终止之前的请求,避免重复发送:
import { useState, useEffect } from 'react'; export function useFetch(url, method, body, param, update) { const [data, setData] = useState({}); const [isLoading, setLoading] = useState(true); const [error, setError] = useState(false); useEffect(() => { if (!url) return; // 创建AbortController实例 const abortController = new AbortController(); const signal = abortController.signal; setLoading(true) setError(false) var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Connection", "Keep-alive"); var myInit = { method: (method ? method : "GET"), headers: myHeaders, mode: 'cors', cache: 'default', body: (body ? JSON.stringify(body) : null), signal // 将信号传入请求配置 }; const allPath = "http://localhost:9000/streengeAPI" + url + (param ? param : ""); async function fetchData() { try { const response = await fetch(allPath, myInit); if (signal.aborted) return; // 如果请求已取消,直接返回 const data = await response.json(); setData(data); } catch (err) { if (err.name !== 'AbortError') { // 忽略取消请求的错误 console.log(err); setError(true); } } finally { if (!signal.aborted) { // 只有请求未取消时才更新状态 setLoading(false); } } } fetchData(); // 组件卸载或依赖变化时,取消请求 return () => abortController.abort(); }, [url, method, body, param, update]) return { isLoading, data, error }; }
2. 稳定body参数的引用
如果body对象的内容不会频繁变化,可以在调用useFetch的组件中使用useMemo来缓存它,避免每次渲染都生成新的引用:
// 在调用useFetch的组件中 const body = useMemo(() => ({ // 你的请求体内容,比如实体数据 id: 'PR0019', ...其他字段 }), [/* 这里放body内容依赖的变量,比如表单输入值 */]); // 然后调用useFetch const { isLoading, data, error } = useFetch('/api/entities', 'POST', body, null, update);
3. 临时关闭Strict Mode(仅开发环境调试用)
如果你只是想快速验证问题是否由Strict Mode导致,可以暂时移除index.js中的<StrictMode>包裹:
// 原来的代码 root.render( <StrictMode> <App /> </StrictMode> ); // 修改后 root.render( <App /> );
⚠️ 注意:这只是临时调试手段,不建议在开发中长期关闭Strict Mode,它能帮你提前发现很多潜在问题。
4. 后端幂等性处理(补充保障)
从后端层面增加幂等性校验,比如要求前端传递唯一的请求ID,或者在创建实体前先检查主键是否已存在,返回友好的错误提示,避免直接抛出数据库约束异常:
// 示例:Spring Boot服务层代码 @Service public class EntityService { @Autowired private EntityRepository repository; public Entity createEntity(Entity entity) { if (repository.existsById(entity.getId())) { throw new RuntimeException("实体ID已存在"); } return repository.save(entity); } }
通过以上方案,你可以彻底解决重复请求导致的主键约束错误问题,同时让你的useFetch钩子更加健壮。
内容的提问来源于stack exchange,提问作者Tadjou Steve
相关产品推荐
相关产品推荐

