setTimeout定时器不生效- useRef 的特点/作用
问题:一个事件,触发后三秒执行,中间如果再次触发,重新计时,但是这个写法不生效,再次点击时,没有重新计时,怎么修改
原写法:
const viewAlert = (val) => {if (timeout) {clearTimeout(timeout)timeout = null}const { content, title, type } = valsetAlertInfo({ content, title, type })setShowAlert(true)timeout = setTimeout(() => {setShowAlert(false)}, 3000)}.
修改后:使用 useRef 来存储 timeout:这样可以避免每次渲染时都重新创建 timeout 变量
const viewAlert = (val) => {if (timeout.current) {clearTimeout(timeout.current);timeout.current = null;}const { content, title, type } = valsetAlertInfo({ content, title, type })setShowAlert(true)timeout.current = setTimeout(() => {setShowAlert(false)}, 3000)}// 清理定时器useEffect(() => {return () => {if (timeoutRef.current) {clearTimeout(timeoutRef.current);}};}, []);
使用普通变量 timeout 时没有生效,但在使用 useRef 存储 timeout 时成功了。
原因:
在这个例子中,每次 viewAlert
被调用时,timeout
都会被重新声明。假设你在第一次点击后设置了 timeout
,然后在 1 秒后再次点击,第二次点击时 timeout
已经被重新声明为 null
,因此 clearTimeout(timeout)
不会清除任何东西,新的 setTimeout
也不会覆盖旧的。
使用 useRef
存储 timeout
是正确的做法,因为它确保了定时器引用的持久性,避免了由于组件重新渲染导致的定时器问题
1️⃣ 函数组件的重新渲染:
在函数组件中,每次状态更新(例如 setAlertInfo
和 setShowAlert
)都会触发组件的重新渲染。
每次重新渲染时,函数内的所有局部变量(包括 timeout
)都会被重新初始化。
2️⃣ timeout
变量的作用域:
当你使用普通变量 timeout
时,每次组件重新渲染时,timeout
都会被重新声明并初始化为 null.
因此,在 setTimeout
设置的回调函数执行之前,timeout
已经被重新初始化为 null
,导致无法正确清除之前的定时器。
为什么 useRef
能解决问题
1️⃣ useRef
的持久性:
useRef
返回的对象在整个组件的生命周期内是持久存在的,不会随着组件的重新渲染而被重新初始化。
这意味着 timeoutRef.current
在多次点击之间保持引用,不会被重新声明为 null
。
2️⃣ 清除和设置定时器:
在 viewAlert
函数中,通过 timeoutRef.current
来清除和设置新的定时器。
即使组件重新渲染,timeoutRef.current
仍然指向同一个引用,因此可以正确地清除之前的定时器并设置新的定时器。