mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6mobile wallpaper 7
372 字
1 分钟
记录一个 React 表单的小坑:缓存节流导致页面刷新
2026-05-10

写了个登录页面,需求很简单,防止用户快速重复点击提交按钮。

很自然的想到了用 throttle 节流,配合 React 的 useMemo 缓存一下。代码写完测试了一下,第一次点正常,第二次点页面直接刷新了。

排查了一下,发现是 useMemo 和 throttle 共同作用的结果。

错误代码#

import { useCallback, useMemo } from 'react'
import { throttle } from 'lodash'
export default function Login() {
const [loading, setLoading] = useState(false)
const handleSubmit = useCallback(async (e) => {
e.preventDefault()
if (loading) return
setLoading(true)
// 登录请求...
await login()
setLoading(false)
}, [])
// 缓存节流函数
const throttledSubmit = useMemo(
() => throttle(handleSubmit, 2000, { leading: true }),
[handleSubmit]
)
return (
<form onSubmit={throttledSubmit}>
<button type="submit">登录</button>
</form>
)
}

看起来没问题,但实际第一次点击正常发起请求,2秒内第二次点击页面直接刷新了。

原因分析#

throttle 工作原理#

throttle 内部维护了一个 timer 变量,记录当前是否处于冷却期:

function throttle(fn, delay) {
let timer = null
return function(...args) {
if (timer) return // 冷却期内直接返回
timer = setTimeout(() => {
timer = null
}, delay)
fn.apply(this, args)
}
}

冷却期内被拦截的调用会直接 return,不会执行原函数。

useMemo 导致实例唯一#

const throttledSubmit = useMemo(
() => throttle(handleSubmit, 2000),
[handleSubmit]
)

useMemo 保证 throttledSubmit 在整个组件生命周期中只有一个实例,timer 变量只初始化一次,节流状态在整个组件中共享,不会因为组件重渲染而重置。

两次点击的执行流程#

第一次点击:

用户点击 → 表单 onSubmit → throttledSubmit(e) 执行
→ timer === null,通过检查
→ 执行 handleSubmit(e)
→ e.preventDefault() 执行
→ 设置 timer,开始 2 秒冷却
→ 页面不刷新

第二次点击(2秒内):

用户点击 → 表单 onSubmit → throttledSubmit(e) 执行
→ timer !== null,直接 return
→ handleSubmit 根本没进去
→ e.preventDefault() 没有执行
→ 浏览器执行表单默认行为
→ 页面刷新

throttle 不仅拦截了业务逻辑,也拦截了 e.preventDefault()。被拦截的调用中,没有任何代码能够阻止表单的默认提交行为。

解决方案#

在 throttle 外部调用 preventDefault:

<form onSubmit={e => {
e.preventDefault()
throttledSubmit(e)
}}>

这样无论 throttle 是否拦截,preventDefault 都会先执行。

或者不用 throttle,用状态锁:

const [loading, setLoading] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
if (loading) return
setLoading(true)
try {
await login()
} finally {
setLoading(false)
}
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

记录一个 React 表单的小坑:缓存节流导致页面刷新
http://mizuki.heycheems.top/posts/记录一个_react_表单的小坑缓存节流导致页面刷新/
作者
heyCHEEMS
发布于
2026-05-10
许可协议
CC BY 4.0

部分信息可能已经过时

目录