为何react-query的useMutation条件校验失效,频繁点击会触发非法更新请求?
问题原因
mutation.isLoading状态更新是非同步的:第一次调用mutation.mutate()后,React 状态更新需要等待下一次渲染周期,连续快速点击时,前几次点击触发时isLoading还未更新为true,会绕过你的加载状态校验,多次提交变更请求。- 你使用了 Firestore 的
increment()增量操作,这个逻辑是在服务端执行的,本地缓存的workoutplan.currentDay要等invalidateQueries触发重新拉取后才会更新,快速点击时多次请求用的都是同一个旧的currentDay值做判断,会认为currentDay>0始终成立,导致多次提交-1的增量请求,最终结果就是currentDay变成负数。
解决方案
- 最简单的方案是直接给切换按钮加禁用状态,在请求过程中、或者当前已经是第一天时禁止点击:
按钮组件上添加属性disabled={mutation.isLoading || workoutplan?.currentDay <= 0},从交互层面杜绝连续重复提交的可能。 - 把非幂等的
increment操作换成直接传入计算好的确定值,保证重复提交也不会出现异常结果:
点击时先本地计算合法的新值,直接传给服务端,示例代码:
这样就算因为状态更新延迟导致多次提交,每次传入的const moveDayBack = () => { if (workoutplan && !mutation.isLoading) { const newCurrentDay = Math.max(0, workoutplan.currentDay - 1); if (newCurrentDay !== workoutplan.currentDay) { mutation.mutate({ currentDay: newCurrentDay }); } } };newCurrentDay都是0,不会出现负数。 - 加乐观更新逻辑,点击后立即修改本地缓存的
currentDay值,后续点击判断用的是更新后的值,从逻辑层面避免错误判断:
在useMutation配置中添加onMutate钩子,提前更新缓存,示例代码:const mutation = useFirestoreDocumentMutation( ref, { merge: true }, { onMutate: async (newData) => { await queryClient.cancelQueries([...]); // 取消正在进行的拉取请求,避免覆盖本地更新 const previousData = queryClient.getQueryData([...]); // 乐观更新本地缓存 queryClient.setQueryData([...], (old) => { if (!old) return old; return { ...old, data: () => ({ ...old.data(), currentDay: Math.max(0, old.data().currentDay -1) }) }; }); return { previousData }; }, onError: (err, newData, context) => { // 请求失败时回滚到之前的状态 queryClient.setQueryData([...], context.previousData); }, onSuccess() { queryClient.invalidateQueries([...]); }, } ); - 后端兜底,给 Firestore 对应集合添加安全规则,限制
currentDay的值必须 >=0 且 <days数组的长度,就算前端校验被绕过,非法请求也会被服务端直接拒绝,不会写入异常数据。
内容的提问来源于stack exchange,提问作者Lukas Vis
相关产品推荐
相关产品推荐

