Next.js + TypeScript项目中API重复调用与分页异常问题求助
问题分析与解决方案
一、API被调用两次的原因
我看了你的代码,发现两个核心问题导致了API重复调用:
useEffect依赖项触发重复执行
你的useEffect依赖数组里包含了detail,而useEffect内部又调用了getDetailRole()——这个函数应该是用来更新detail状态的对吧?那流程就是:- 组件挂载时,
useEffect因为id/form/detail初始值触发,调用getRolePermission(第一次API请求) getDetailRole执行完成后更新detail状态,依赖项detail变化,导致useEffect再次执行,又调用了一次getRolePermission(第二次API请求)
- 组件挂载时,
重复声明router变量
你写了两次const router = useRouter();,虽然这不会直接导致API调用两次,但属于冗余代码,建议删掉其中一个。
二、分页功能失效的原因
分页异常主要是因为状态更新的异步特性导致的状态不同步,还有逻辑上的小漏洞:
状态更新依赖旧值,未使用函数式更新
你在更新rolePermission和pagination的时候,直接依赖了当前的状态变量:setRolePermission(rps.page === 1 ? roleP : [...rolePermission, ...roleP]); setPagination({ ...pagination, page: pagination.page + 1 });因为React的
setState是异步的,当连续触发分页加载时,这里拿到的rolePermission和pagination可能还是旧值,导致数据拼接错误、页码计算混乱。useEffect重复触发打乱分页状态
刚才说的useEffect因为detail变化重复执行,会重新调用getRolePermission,这会重置分页的状态(比如把页码又改回1),自然会导致分页逻辑混乱。缺少对
nextPage的判断逻辑
当nextPage为0(没有更多数据)时,你的代码没有停止加载的判断,可能会继续发起无效请求,或者导致重复加载同一页数据。
三、修复后的代码示例
我把这些问题都修复了,你可以参考:
const EditRoles = () => { const router = useRouter(); // 删掉重复的声明 const { id } = router.query; const [detail, setDetail] = useState<RoleDetail | undefined>(); // 增加类型定义 const [rolePermission, setRolePermission] = useState<RolePermissionItem[]>([]); // 增加类型定义 const [pagination, setPagination] = useState<Params>({ page: 1, row: 10, }); const [loading, setLoading] = useState(false); const [nextPage, setNextPage] = useState(1); // 初始化nextPage为1,确保首次加载触发 const getDetailRole = async () => { if (!id) return; try { const res = await httpService.get(`${apiUrl.user}/v1/role/${id}`); setDetail(res.data); form.setFieldsValue({ name: res.data }); } catch (err) { console.error('获取角色详情失败:', err); } }; const getRolePermission = async (payload: { page?: number; row?: number; search?: string }) => { if (!id || loading || nextPage === 0) return; // 增加拦截条件,避免无效请求 setLoading(true); try { const res = await httpService.get(`${apiUrl.user}/v1/role-permission/${id}`, { params: payload }); const rps = res.data; // 使用函数式更新,确保拿到最新的状态 setRolePermission(prev => rps.page === 1 ? rps.data : [...prev, ...rps.data]); setNextPage(rps.nextPage); setPagination(prev => ({ ...prev, page: prev.page + 1 })); } catch (err) { console.error('获取权限列表失败:', err); } finally { setLoading(false); } console.log('this next page: ', rps.nextPage); }; useEffect(() => { if (!id) return; getDetailRole(); // 初始化分页状态,只在id变化时触发 setRolePermission([]); setPagination({ page: 1, row: 10 }); setNextPage(1); getRolePermission({ page: 1, row: 10 }); }, [id]); // 移除detail和form依赖,避免重复触发 // 单独处理表单赋值,避免依赖项混乱 useEffect(() => { if (detail && rolePermission.length > 0) { form.setFieldsValue({ name: detail, permissions: rolePermission }); } }, [detail, rolePermission, form]); return ( // ... HTML GOES HERE ... ); }; export default EditRoles;
额外建议
- 给所有
useState加上明确的类型定义,避免TypeScript类型报错 - 分页加载更多时,一定要通过
loading状态拦截重复请求,防止并发问题 - 可以把分页逻辑封装成自定义Hook,让组件代码更简洁易维护
内容的提问来源于stack exchange,提问作者Miko
相关产品推荐
相关产品推荐

