From 3c3f147ba32efd74054b5a7709c272d53df8ef47 Mon Sep 17 00:00:00 2001 From: xuejy <30921318+titxue@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:48:51 +0800 Subject: [PATCH] perf: align TOTP timer to second boundary and guard async races --- src/hooks/useTOTP.ts | 57 +++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/hooks/useTOTP.ts b/src/hooks/useTOTP.ts index 8c6899f..88b9213 100644 --- a/src/hooks/useTOTP.ts +++ b/src/hooks/useTOTP.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import type { Account } from '@/types' import { TOTP } from '@/utils/totp' @@ -12,10 +12,14 @@ interface TOTPCodes { export function useTOTP(accounts: Account[]) { const [codes, setCodes] = useState({}) const [remaining, setRemaining] = useState(30) - const animationFrameRef = useRef(undefined) + const timeoutRef = useRef(undefined) + const intervalRef = useRef(undefined) + const lastStepRef = useRef(-1) + const generationIdRef = useRef(0) // 生成所有账户的验证码 - const generateCodes = async () => { + const generateCodes = useCallback(async () => { + const currentGenerationId = ++generationIdRef.current const newCodes: TOTPCodes = {} for (const account of accounts) { @@ -27,40 +31,45 @@ export function useTOTP(accounts: Account[]) { } } - setCodes(newCodes) - } + // 避免异步竞态导致旧结果覆盖新结果 + if (generationIdRef.current === currentGenerationId) { + setCodes(newCodes) + } + }, [accounts]) - // 使用 requestAnimationFrame 实现精确的定时更新 + // 使用秒级刷新策略:先对齐到下一秒边界,再每秒更新一次 useEffect(() => { - let lastTimestamp = Date.now() - const updateTimer = () => { - const currentTimestamp = Date.now() - const currentRemaining = TOTP.getRemainingSeconds() + const currentStep = Math.floor(Date.now() / 30000) + setRemaining(TOTP.getRemainingSeconds()) - // 检测是否需要重新生成验证码(跨越30秒边界) - if (Math.floor(lastTimestamp / 30000) !== Math.floor(currentTimestamp / 30000)) { + if (lastStepRef.current !== currentStep) { + lastStepRef.current = currentStep generateCodes() } - - setRemaining(currentRemaining) - lastTimestamp = currentTimestamp - - animationFrameRef.current = requestAnimationFrame(updateTimer) } - // 立即生成一次验证码 - generateCodes() + // 初始化:立即同步剩余时间与验证码 + updateTimer() + + // 对齐到下一秒边界,减少时间漂移 + const now = Date.now() + const msToNextSecond = 1000 - (now % 1000) - // 开始动画循环 - animationFrameRef.current = requestAnimationFrame(updateTimer) + timeoutRef.current = window.setTimeout(() => { + updateTimer() + intervalRef.current = window.setInterval(updateTimer, 1000) + }, msToNextSecond) return () => { - if (animationFrameRef.current) { - cancelAnimationFrame(animationFrameRef.current) + if (timeoutRef.current !== undefined) { + clearTimeout(timeoutRef.current) + } + if (intervalRef.current !== undefined) { + clearInterval(intervalRef.current) } } - }, [accounts]) + }, [generateCodes]) return { codes,