diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index 37323b5cf..d02ce36de 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -444,6 +444,12 @@ export const CurrentProxyCard = () => { [setState], ) + useEffect(() => { + return () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current) + } + }, []) + // 处理代理组变更 const handleGroupChange = useCallback( (event: SelectChangeEvent) => { diff --git a/src/components/home/ip-info-card.tsx b/src/components/home/ip-info-card.tsx index 16d37309b..4252ca16e 100644 --- a/src/components/home/ip-info-card.tsx +++ b/src/components/home/ip-info-card.tsx @@ -425,7 +425,7 @@ function useIPInfo() { queryKey: [IP_INFO_CACHE_KEY], queryFn: getIpInfo, staleTime: Infinity, - gcTime: Infinity, + gcTime: 60 * 60 * 1000, refetchOnWindowFocus: false, refetchOnReconnect: false, retry: 1, diff --git a/src/components/profile/editor-viewer.tsx b/src/components/profile/editor-viewer.tsx index 43d3dec63..8e85145d1 100644 --- a/src/components/profile/editor-viewer.tsx +++ b/src/components/profile/editor-viewer.tsx @@ -168,6 +168,13 @@ export const EditorViewer = ({ } }, [open, syncMaximizedState]) + useEffect(() => { + return () => { + editorRef.current?.dispose() + editorRef.current = null + } + }, []) + return ( { [t], ) const themeMode = useThemeMode() + const editorRef = useRef(null) const [prevData, setPrevData] = useState('') const [currData, setCurrData] = useState('') const [visualization, setVisualization] = useState(true) @@ -481,6 +484,13 @@ export const GroupsEditorViewer = (props: Props) => { getInterfaceNameList() }, [fetchContent, fetchProfile, getInterfaceNameList, open]) + useEffect(() => { + return () => { + editorRef.current?.dispose() + editorRef.current = null + } + }, []) + const validateGroup = () => { const group = formIns.getValues() if (group.name === '') { @@ -1105,6 +1115,9 @@ export const GroupsEditorViewer = (props: Props) => { language="yaml" value={currData} theme={themeMode === 'light' ? 'light' : 'vs-dark'} + onMount={(editorInstance) => { + editorRef.current = editorInstance + }} options={{ tabSize: 2, // 根据语言类型设置缩进大小 minimap: { diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 504a40c2d..e0ce78e30 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -19,7 +19,7 @@ import { import { open } from '@tauri-apps/plugin-shell' import { useLockFn } from 'ahooks' import dayjs from 'dayjs' -import { useCallback, useEffect, useReducer, useState } from 'react' +import { useCallback, useEffect, useReducer, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { ConfirmViewer } from '@/components/profile/confirm-viewer' @@ -96,7 +96,11 @@ export const ProfileItem = (props: Props) => { // 新增状态:是否显示下次更新时间 const [showNextUpdate, setShowNextUpdate] = useState(false) + const showNextUpdateRef = useRef(false) const [nextUpdateTime, setNextUpdateTime] = useState('') + const refreshTimeoutRef = useRef | undefined>( + undefined, + ) const { uid, name = 'Profile', extra, updated = 0, option } = itemData @@ -178,6 +182,10 @@ export const ProfileItem = (props: Props) => { setShowNextUpdate(!showNextUpdate) } + useEffect(() => { + showNextUpdateRef.current = showNextUpdate + }, [showNextUpdate]) + // 当组件加载或更新间隔变化时更新下次更新时间 useEffect(() => { if (showNextUpdate) { @@ -192,19 +200,18 @@ export const ProfileItem = (props: Props) => { // 订阅定时器更新事件 useEffect(() => { - let refreshTimeout: number | undefined // 处理定时器更新事件 - 这个事件专门用于通知定时器变更 const handleTimerUpdate = (event: Event) => { const source = event as CustomEvent & { payload?: string } const updatedUid = source.detail ?? source.payload // 只有当更新的是当前配置时才刷新显示 - if (updatedUid === itemData.uid && showNextUpdate) { + if (updatedUid === itemData.uid && showNextUpdateRef.current) { debugLog(`收到定时器更新事件: uid=${updatedUid}`) - if (refreshTimeout !== undefined) { - clearTimeout(refreshTimeout) + if (refreshTimeoutRef.current !== undefined) { + clearTimeout(refreshTimeoutRef.current) } - refreshTimeout = window.setTimeout(() => { + refreshTimeoutRef.current = window.setTimeout(() => { fetchNextUpdateTime(true) }, 1000) } @@ -214,13 +221,13 @@ export const ProfileItem = (props: Props) => { window.addEventListener('verge://timer-updated', handleTimerUpdate) return () => { - if (refreshTimeout !== undefined) { - clearTimeout(refreshTimeout) + if (refreshTimeoutRef.current !== undefined) { + clearTimeout(refreshTimeoutRef.current) } // 清理事件监听 window.removeEventListener('verge://timer-updated', handleTimerUpdate) } - }, [fetchNextUpdateTime, itemData.uid, showNextUpdate]) + }, [fetchNextUpdateTime, itemData.uid]) // local file mode // remote file mode diff --git a/src/components/profile/proxies-editor-viewer.tsx b/src/components/profile/proxies-editor-viewer.tsx index 4431bb6fc..20f64abf4 100644 --- a/src/components/profile/proxies-editor-viewer.tsx +++ b/src/components/profile/proxies-editor-viewer.tsx @@ -27,11 +27,13 @@ import { } from '@mui/material' import { useLockFn } from 'ahooks' import yaml from 'js-yaml' +import type { editor } from 'monaco-editor' import { startTransition, useCallback, useEffect, useMemo, + useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' @@ -56,6 +58,7 @@ export const ProxiesEditorViewer = (props: Props) => { const { profileUid, property, open, onClose, onSave } = props const { t } = useTranslation() const themeMode = useThemeMode() + const editorRef = useRef(null) const [prevData, setPrevData] = useState('') const [currData, setCurrData] = useState('') const [visualization, setVisualization] = useState(true) @@ -343,6 +346,13 @@ export const ProxiesEditorViewer = (props: Props) => { fetchProfile() }, [fetchContent, fetchProfile, open]) + useEffect(() => { + return () => { + editorRef.current?.dispose() + editorRef.current = null + } + }, []) + const handleSave = useLockFn(async () => { try { await saveProfileFile(property, currData) @@ -469,6 +479,9 @@ export const ProxiesEditorViewer = (props: Props) => { language="yaml" value={currData} theme={themeMode === 'light' ? 'light' : 'vs-dark'} + onMount={(editorInstance) => { + editorRef.current = editorInstance + }} options={{ tabSize: 2, // 根据语言类型设置缩进大小 minimap: { diff --git a/src/components/profile/rules-editor-viewer.tsx b/src/components/profile/rules-editor-viewer.tsx index be190892a..ce4277e1c 100644 --- a/src/components/profile/rules-editor-viewer.tsx +++ b/src/components/profile/rules-editor-viewer.tsx @@ -29,11 +29,13 @@ import { } from '@mui/material' import { useLockFn } from 'ahooks' import yaml from 'js-yaml' +import type { editor } from 'monaco-editor' import { startTransition, useCallback, useEffect, useMemo, + useRef, useState, } from 'react' import { useTranslation } from 'react-i18next' @@ -251,6 +253,8 @@ export const RulesEditorViewer = (props: Props) => { const { t } = useTranslation() const themeMode = useThemeMode() + const editorRef = useRef(null) + const [prevData, setPrevData] = useState('') const [currData, setCurrData] = useState('') const [visualization, setVisualization] = useState(true) @@ -536,6 +540,13 @@ export const RulesEditorViewer = (props: Props) => { fetchProfile() }, [fetchContent, fetchProfile, open]) + useEffect(() => { + return () => { + editorRef.current?.dispose() + editorRef.current = null + } + }, []) + const validateRule = () => { if ((ruleType.required ?? true) && !ruleContent) { throw new Error( @@ -770,6 +781,9 @@ export const RulesEditorViewer = (props: Props) => { language="yaml" value={currData} theme={themeMode === 'light' ? 'light' : 'vs-dark'} + onMount={(editorInstance) => { + editorRef.current = editorInstance + }} options={{ tabSize: 2, // 根据语言类型设置缩进大小 minimap: { diff --git a/src/components/setting/mods/dns-viewer.tsx b/src/components/setting/mods/dns-viewer.tsx index 04563302c..741b2ca8d 100644 --- a/src/components/setting/mods/dns-viewer.tsx +++ b/src/components/setting/mods/dns-viewer.tsx @@ -16,6 +16,7 @@ import { import { invoke } from '@tauri-apps/api/core' import { useLockFn } from 'ahooks' import yaml from 'js-yaml' +import type { editor } from 'monaco-editor' import type { Ref } from 'react' import { useCallback, @@ -189,6 +190,7 @@ export function DnsViewer({ ref }: { ref?: Ref }) { const [open, setOpen] = useState(false) const [visualization, setVisualization] = useState(true) const skipYamlSyncRef = useRef(false) + const editorRef = useRef(null) const [values, setValues] = useState<{ enable: boolean listen: string @@ -453,6 +455,13 @@ export function DnsViewer({ ref }: { ref?: Ref }) { } }, [visualization]) + useEffect(() => { + return () => { + editorRef.current?.dispose() + editorRef.current = null + } + }, []) + const initDnsConfig = useCallback(async () => { try { const dnsConfigExists = await invoke( @@ -1057,6 +1066,9 @@ export function DnsViewer({ ref }: { ref?: Ref }) { value={yamlContent} theme={themeMode === 'light' ? 'light' : 'vs-dark'} className="flex-grow" + onMount={(editorInstance) => { + editorRef.current = editorInstance + }} options={{ tabSize: 2, minimap: { diff --git a/src/hooks/use-icon-cache.ts b/src/hooks/use-icon-cache.ts index e42ce51c3..9faf49052 100644 --- a/src/hooks/use-icon-cache.ts +++ b/src/hooks/use-icon-cache.ts @@ -42,7 +42,7 @@ export const useIconCache = ({ refetchOnWindowFocus: false, refetchOnReconnect: false, staleTime: Infinity, - gcTime: Infinity, + gcTime: 30 * 60 * 1000, retry: 2, }) diff --git a/src/hooks/use-mihomo-ws-subscription.ts b/src/hooks/use-mihomo-ws-subscription.ts index 64ccbfe76..f27df80b7 100644 --- a/src/hooks/use-mihomo-ws-subscription.ts +++ b/src/hooks/use-mihomo-ws-subscription.ts @@ -93,7 +93,7 @@ export const useMihomoWsSubscription = ( subscriptionCacheKey ?? '$sub$__disabled__', ]) ?? fallbackData, staleTime: Infinity, - gcTime: Infinity, + gcTime: 30_000, enabled: subscriptionCacheKey !== null, }) @@ -243,8 +243,11 @@ export const useMihomoWsSubscription = ( }, [subscriptionCacheKey]) const refresh = useCallback(() => { + if (subscriptionCacheKey) { + queryClient.removeQueries({ queryKey: [subscriptionCacheKey] }) + } setDate(Date.now()) - }, [setDate]) + }, [queryClient, subscriptionCacheKey, setDate]) return { response, refresh, subscriptionCacheKey, wsRef } } diff --git a/src/pages/_layout/hooks/use-custom-theme.ts b/src/pages/_layout/hooks/use-custom-theme.ts index cbded85ed..a00c1b67b 100644 --- a/src/pages/_layout/hooks/use-custom-theme.ts +++ b/src/pages/_layout/hooks/use-custom-theme.ts @@ -309,20 +309,22 @@ export const useCustomTheme = () => { styleElement.innerHTML = effectiveInjectedCss + globalStyles } - const { palette } = muiTheme - setTimeout(() => { - const dom = document.querySelector('#Gradient2') - if (dom) { - dom.innerHTML = ` - - - - ` - } - }, 0) - return muiTheme }, [mode, theme_setting, userBackgroundImage, hasUserBackground]) + useEffect(() => { + const id = setTimeout(() => { + const dom = document.querySelector('#Gradient2') + if (dom) { + dom.innerHTML = ` + + + + ` + } + }, 0) + return () => clearTimeout(id) + }, [theme.palette.primary.main, theme.palette.primary.dark]) + return { theme } }