diff --git a/src/hooks/use-proxy-delay-state.ts b/src/hooks/use-proxy-delay-state.ts index 66913f281..0e37be0f5 100644 --- a/src/hooks/use-proxy-delay-state.ts +++ b/src/hooks/use-proxy-delay-state.ts @@ -1,5 +1,5 @@ import { useLockFn } from 'ahooks' -import { useCallback, useEffect, useReducer } from 'react' +import { useCallback, useSyncExternalStore } from 'react' import { useVerge } from '@/hooks/use-verge' import delayManager, { type DelayUpdate } from '@/services/delay' @@ -17,13 +17,6 @@ const PRESET_PROXY_NAMES = [ 'COMPATIBLE', ] -const identity = (previous: DelayUpdate, next: DelayUpdate): DelayUpdate => - previous.delay === next.delay && - previous.elapsed === next.elapsed && - previous.updatedAt === next.updatedAt - ? previous - : next - const INITIAL_DELAY: DelayUpdate = { delay: -1, updatedAt: 0 } export interface UseProxyDelayState { @@ -42,48 +35,33 @@ export function useProxyDelayState( const details = memberDetails(member) const unresolved = member.kind === 'unresolved' const isPreset = unresolved || PRESET_PROXY_NAMES.includes(name) - const [delayState, setDelayState] = useReducer(identity, INITIAL_DELAY) const { verge } = useVerge() const timeout = verge?.default_latency_timeout || 10000 - useEffect(() => { - if (isPreset) return - return delayManager.addListener(name, groupName, setDelayState) - }, [name, groupName, isPreset]) + const subscribe = useCallback( + (onChange: () => void) => + isPreset ? () => {} : delayManager.addListener(name, groupName, onChange), + [name, groupName, isPreset], + ) + const read = useCallback( + () => + unresolved ? undefined : delayManager.getDelayUpdate(name, groupName), + [name, groupName, unresolved], + ) + const cachedUpdate = useSyncExternalStore(subscribe, read, read) - const updateDelay = useCallback(() => { - if (unresolved) { - setDelayState(INITIAL_DELAY) - return - } - const cachedUpdate = delayManager.getDelayUpdate(name, groupName) - if (cachedUpdate) { - setDelayState({ ...cachedUpdate }) - return - } - - const fallbackDelay = delayManager.getDelayFix(member, groupName) - if (fallbackDelay === -1) { - setDelayState({ delay: -1, updatedAt: 0 }) - return - } - - let updatedAt = 0 - const history = details?.history - if (history && history.length > 0) { - const lastRecord = history[history.length - 1] - const parsed = Date.parse(lastRecord.time) - if (!Number.isNaN(parsed)) { - updatedAt = parsed - } - } - - setDelayState({ delay: fallbackDelay, updatedAt }) - }, [details?.history, groupName, member, name, unresolved]) - - useEffect(() => { - updateDelay() - }, [updateDelay]) + const fallbackDelay = delayManager.getDelayFix(member, groupName) + const history = details?.history + const lastRecord = history?.[history.length - 1] + const parsed = lastRecord ? Date.parse(lastRecord.time) : NaN + const delayState = + cachedUpdate ?? + (fallbackDelay === -1 + ? INITIAL_DELAY + : { + delay: fallbackDelay, + updatedAt: Number.isNaN(parsed) ? 0 : parsed, + }) const onDelay = useLockFn(async () => { if (!isInteractableMember(member)) return diff --git a/src/services/delay.test.ts b/src/services/delay.test.ts index e7ea32bf0..ebdf8414c 100644 --- a/src/services/delay.test.ts +++ b/src/services/delay.test.ts @@ -74,9 +74,12 @@ describe('profile delay isolation', () => { const stopSecond = delayManager.addListener('a', 'g', second) try { delayManager.setDelay('a', 'g', -2) + const pending = delayManager.getDelayUpdate('a', 'g') + expect(delayManager.getDelayUpdate('a', 'g')).toBe(pending) await flush() stopFirst() delayManager.setDelay('a', 'g', 120) + expect(delayManager.getDelayUpdate('a', 'g')).not.toBe(pending) await flush() expect(first.mock.calls.map(([update]) => update.delay)).toEqual([-2]) expect(second.mock.calls.map(([update]) => update.delay)).toEqual([ diff --git a/src/services/delay.ts b/src/services/delay.ts index 48b5f6e39..67db25cb8 100644 --- a/src/services/delay.ts +++ b/src/services/delay.ts @@ -274,7 +274,8 @@ class DelayManager { return undefined } - return { ...entry } + // React's external-store reads must retain identity until the value changes. + return entry } getDelay(name: string, group: string) {