React组件中如何同时使用两个不同定时的setInterval?
问题描述
我有一个VerifyScreen页面,用户未验证邮箱时会被引导至该页面。需要实现两个定时功能:
- 每5秒检查一次用户是否完成邮箱验证
- 邮件重发按钮的倒计时,归零后用户可重新发送邮件
单独使用任意一个定时器时功能正常,但二者无法同时运行:点击重发按钮触发倒计时后,用户检查定时器会暂停,直到倒计时结束才恢复。后来我把用户检查的setInterval移到useEffect外部,两个定时器能同时运行了,但userTimer有时会在控制台触发两次,不确定是模拟器问题还是代码错误。
原逻辑代码:
const [emailTimer, setEmailTimer] = useState(0); const [, setUser] = useAtom(userAtom); const userTimer = setInterval(async () => { const refreshedUser = auth().currentUser; console.log('checked user'); if (refreshedUser?.emailVerified) { clearInterval(userTimer); setUser(refreshedUser); } else { await auth().currentUser?.reload(); } }, 10000); useEffect(() => { const resendEmailTimer = setInterval(() => { if (emailTimer <= 0) { clearInterval(resendEmailTimer); } else { console.log('second subtracted'); setEmailTimer(emailTimer - 1); } }, 1000); return () => { clearInterval(userTimer); clearInterval(resendEmailTimer); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [emailTimer]);
邮件重发按钮组件代码:
<View style={{ paddingBottom: 10 }}> <PrimaryButton disabled={emailTimer > 0} onPress={async () => { setEmailTimer(60); await handleSendEmailConfirmation(); }}> <CustomText text={emailTimer > 0 ? `${emailTimer}` : 'Resend email'} color={theme.colors.white} /> </PrimaryButton> </View> <OutlinedButton onPress={async () => { await handleLogOut(); }}> <CustomText text="Cancel" /> </OutlinedButton>
问题分析与修复
核心问题
- 用户检查定时器放在组件顶层,每次组件重渲染(比如
emailTimer变化)都会创建新的定时器,导致多个定时器同时运行,出现重复打印的问题。 - 倒计时定时器依赖
emailTimer,每次倒计时更新都会重建定时器,且清理函数会清除用户检查定时器,导致其被意外终止。
修复方案
- 将用户检查定时器放到独立的
useEffect中,仅在组件挂载时创建、卸载时清除,避免重复创建。 - 倒计时定时器使用函数式更新
setEmailTimer(prev => prev - 1),避免依赖emailTimer导致频繁重建定时器,同时保证拿到最新的倒计时数值。
修正后的完整代码
import { useState, useEffect } from 'react'; import { useAtom } from 'jotai'; import { auth } from '../your-auth-config'; // 替换为你的auth配置路径 import { userAtom } from '../your-atom-config'; // 替换为你的atom配置路径 const VerifyScreen = () => { const [emailTimer, setEmailTimer] = useState(0); const [, setUser] = useAtom(userAtom); // 仅在组件挂载时启动用户验证检查定时器 useEffect(() => { const userCheckTimer = setInterval(async () => { const currentUser = auth().currentUser; console.log('checked user'); if (!currentUser) return; // 先刷新用户信息,确保拿到最新状态 await currentUser.reload(); const refreshedUser = auth().currentUser; if (refreshedUser?.emailVerified) { clearInterval(userCheckTimer); setUser(refreshedUser); } }, 5000); // 改为需求的5秒检查间隔 return () => clearInterval(userCheckTimer); }, [setUser]); // 重发邮件倒计时定时器 useEffect(() => { if (emailTimer <= 0) return; const resendTimer = setInterval(() => { setEmailTimer(prev => { const newTime = prev - 1; // 倒计时归零时自动清除定时器 if (newTime <= 0) clearInterval(resendTimer); return newTime; }); }, 1000); return () => clearInterval(resendTimer); }, [emailTimer]); // 重发邮件逻辑 const handleSendEmailConfirmation = async () => { const currentUser = auth().currentUser; if (currentUser) await currentUser.sendEmailVerification(); }; // 登出逻辑 const handleLogOut = async () => { await auth().signOut(); // 可在此添加跳转至登录页的逻辑 }; return ( <View> {/* 邮件重发与取消按钮 */} <View style={{ paddingBottom: 10 }}> <PrimaryButton disabled={emailTimer > 0} onPress={async () => { setEmailTimer(60); await handleSendEmailConfirmation(); }}> <CustomText text={emailTimer > 0 ? `${emailTimer}` : 'Resend email'} color={theme.colors.white} /> </PrimaryButton> </View> <OutlinedButton onPress={handleLogOut}> <CustomText text="Cancel" /> </OutlinedButton> </View> ); }; export default VerifyScreen;
关键修复点
- 用户检查定时器与倒计时定时器完全独立,互不干扰,各自在专属的
useEffect中管理生命周期。 - 倒计时使用函数式更新解决闭包问题,确保每次更新都基于最新的倒计时数值。
- 优化用户检查逻辑:先调用
reload刷新用户信息,再获取最新状态,保证验证状态的准确性。
内容的提问来源于stack exchange,提问作者user14263509
相关产品推荐
相关产品推荐

