372 字
1 分钟
记录一个 React 表单的小坑:缓存节流导致页面刷新
写了个登录页面,需求很简单,防止用户快速重复点击提交按钮。
很自然的想到了用 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_表单的小坑缓存节流导致页面刷新/ 部分信息可能已经过时
相关文章 智能推荐











