From 9bcb79465cc3c9222f6a33aa5755469073dad9ac Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Tue, 31 Mar 2026 20:26:34 +0800 Subject: [PATCH] fix: resolve frontend data race conditions in hooks - use-system-state: convert module-level `disablingTunMode` to useRef to isolate state per hook instance, fix no-op clearTimeout, add proper effect cleanup - use-profiles: convert forEach to for..of so selectNodeForGroup is properly awaited, remove fire-and-forget setTimeout around mutate - use-clash: add useLockFn to patchInfo for concurrency safety --- src/hooks/use-clash.ts | 4 ++-- src/hooks/use-profiles.ts | 25 +++++++++++++++---------- src/hooks/use-system-state.ts | 27 ++++++++++++++++++--------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/hooks/use-clash.ts b/src/hooks/use-clash.ts index e60c49546..989b086de 100644 --- a/src/hooks/use-clash.ts +++ b/src/hooks/use-clash.ts @@ -87,7 +87,7 @@ export const useClashInfo = () => { getClashInfo, ) - const patchInfo = async (patch: ClashInfoPatch) => { + const patchInfo = useLockFn(async (patch: ClashInfoPatch) => { if (!hasClashInfoPayload(patch)) return validatePorts(patch) @@ -95,7 +95,7 @@ export const useClashInfo = () => { await patchClashConfig(patch) mutateInfo() mutate('getClashConfig') - } + }) return { clashInfo, diff --git a/src/hooks/use-profiles.ts b/src/hooks/use-profiles.ts index 101f9db3f..40e76863e 100644 --- a/src/hooks/use-profiles.ts +++ b/src/hooks/use-profiles.ts @@ -120,9 +120,9 @@ export const useProfiles = () => { ]) // 处理所有代理组 - ;[global, ...groups].forEach((group) => { + for (const group of [global, ...groups]) { if (!group) { - return + continue } const { type, name, now } = group @@ -134,14 +134,14 @@ export const useProfiles = () => { const preferredProxy = now ? now : savedProxy newSelected.push({ name, now: preferredProxy }) } - return + continue } if (savedProxy == null) { if (now != null) { newSelected.push({ name, now }) } - return + continue } const existsInGroup = availableProxies.some((proxy) => { @@ -158,7 +158,7 @@ export const useProfiles = () => { ) hasChange = true newSelected.push({ name, now: now ?? savedProxy }) - return + continue } if (savedProxy !== now) { @@ -166,11 +166,18 @@ export const useProfiles = () => { `[ActivateSelected] 需要切换代理组 ${name}: ${now} -> ${savedProxy}`, ) hasChange = true - selectNodeForGroup(name, savedProxy) + try { + await selectNodeForGroup(name, savedProxy) + } catch (error: any) { + console.warn( + `[ActivateSelected] 切换代理组 ${name} 失败:`, + error.message, + ) + } } newSelected.push({ name, now: savedProxy }) - }) + } if (!hasChange) { debugLog('[ActivateSelected] 所有代理选择已经是目标状态,无需更新') @@ -183,9 +190,7 @@ export const useProfiles = () => { await patchProfile(profileData.current!, { selected: newSelected }) debugLog('[ActivateSelected] 代理选择配置保存成功') - setTimeout(() => { - mutate('getProxies', calcuProxies()) - }, 100) + await mutate('getProxies', calcuProxies()) } catch (error: any) { console.error('[ActivateSelected] 保存代理选择配置失败:', error.message) } diff --git a/src/hooks/use-system-state.ts b/src/hooks/use-system-state.ts index 4b82c4244..4300e2490 100644 --- a/src/hooks/use-system-state.ts +++ b/src/hooks/use-system-state.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import useSWR from 'swr' import { getRunningMode, isAdmin, isServiceAvailable } from '@/services/cmds' @@ -18,14 +18,13 @@ const defaultSystemState = { isServiceOk: false, } as SystemState -let disablingTunMode = false - /** * 自定义 hook 用于获取系统运行状态 * 包括运行模式、管理员状态、系统服务是否可用 */ export function useSystemState() { const { verge, patchVerge } = useVerge() + const disablingTunRef = useRef(false) const { data: systemState, @@ -53,16 +52,18 @@ export function useSystemState() { const isTunModeAvailable = systemState.isAdminMode || systemState.isServiceOk const enable_tun_mode = verge?.enable_tun_mode + const cooldownTimerRef = useRef | null>(null) + useEffect(() => { if (enable_tun_mode === undefined) return if ( - !disablingTunMode && + !disablingTunRef.current && enable_tun_mode && !isTunModeAvailable && !isLoading ) { - disablingTunMode = true + disablingTunRef.current = true patchVerge({ enable_tun_mode: false }) .then(() => { showNotice.info( @@ -76,13 +77,21 @@ export function useSystemState() { ) }) .finally(() => { - const tid = setTimeout(() => { - // 避免 verge 数据更新不及时导致重复执行关闭 Tun 模式 - disablingTunMode = false - clearTimeout(tid) + // 避免 verge 数据更新不及时导致重复执行关闭 Tun 模式 + cooldownTimerRef.current = setTimeout(() => { + disablingTunRef.current = false + cooldownTimerRef.current = null }, 1000) }) } + + return () => { + if (cooldownTimerRef.current != null) { + clearTimeout(cooldownTimerRef.current) + cooldownTimerRef.current = null + disablingTunRef.current = false + } + } }, [enable_tun_mode, isTunModeAvailable, patchVerge, isLoading]) return {