refactor(app-data): split monolithic context into focused SWR hooks (#5576)

* refactor(app-data): split monolithic context into focused SWR hooks

* refactor(swr): unify polling and consolidate proxy/config/provider data flow
This commit is contained in:
Sline
2025-11-24 16:18:31 +08:00
committed by GitHub
parent 871881c460
commit 8e8182f707
17 changed files with 432 additions and 295 deletions
+15 -2
View File
@@ -3,8 +3,14 @@ import { Divider, Stack, Typography } from "@mui/material";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import {
useAppUptime,
useClashConfig,
useRulesData,
useSystemProxyAddress,
useSystemProxyData,
} from "@/hooks/app-data";
import { useClash } from "@/hooks/use-clash";
import { useAppData } from "@/providers/app-data-context";
import { EnhancedCard } from "./enhanced-card";
@@ -19,7 +25,14 @@ const formatUptime = (uptimeMs: number) => {
export const ClashInfoCard = () => {
const { t } = useTranslation();
const { version: clashVersion } = useClash();
const { clashConfig, rules, uptime, systemProxyAddress } = useAppData();
const { clashConfig } = useClashConfig();
const { sysproxy } = useSystemProxyData();
const { rules } = useRulesData();
const { uptime } = useAppUptime();
const systemProxyAddress = useSystemProxyAddress({
clashConfig,
sysproxy,
});
// 使用useMemo缓存格式化后的uptime,避免频繁计算
const formattedUptime = useMemo(() => formatUptime(uptime), [uptime]);
+2 -2
View File
@@ -9,8 +9,8 @@ import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { closeAllConnections } from "tauri-plugin-mihomo-api";
import { useClashConfig } from "@/hooks/app-data";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-context";
import { patchClashMode } from "@/services/cmds";
import type { TranslationKey } from "@/types/generated/i18n-keys";
@@ -41,7 +41,7 @@ const MODE_META: Record<
export const ClashModeCard = () => {
const { t } = useTranslation();
const { verge } = useVerge();
const { clashConfig, refreshClashConfig } = useAppData();
const { clashConfig, refreshClashConfig } = useClashConfig();
// 支持的模式列表
const modeList = CLASH_MODES;
+4 -2
View File
@@ -34,10 +34,10 @@ import { useNavigate } from "react-router";
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
import { EnhancedCard } from "@/components/home/enhanced-card";
import { useClashConfig, useProxiesData, useRulesData } from "@/hooks/app-data";
import { useProfiles } from "@/hooks/use-profiles";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-context";
import delayManager from "@/services/delay";
// 本地存储的键名
@@ -100,7 +100,9 @@ export const CurrentProxyCard = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const theme = useTheme();
const { proxies, clashConfig, refreshProxy, rules } = useAppData();
const { proxies, refreshProxy } = useProxiesData();
const { clashConfig } = useClashConfig();
const { rules } = useRulesData();
const { verge } = useVerge();
const { current: currentProfile } = useProfiles();
const autoDelayEnabled = verge?.enable_auto_delay_detection ?? false;
+2 -2
View File
@@ -24,7 +24,7 @@ import { useCallback, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { useAppData } from "@/providers/app-data-context";
import { useRefreshAll } from "@/hooks/app-data";
import { openWebUrl, updateProfile } from "@/services/cmds";
import { showNotice } from "@/services/noticeService";
import parseTraffic from "@/utils/parse-traffic";
@@ -281,7 +281,7 @@ export const HomeProfileCard = ({
}: HomeProfileCardProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const { refreshAll } = useAppData();
const refreshAll = useRefreshAll();
// 更新当前订阅
const [updating, setUpdating] = useState(false);
+5 -4
View File
@@ -22,7 +22,7 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { updateProxyProvider } from "tauri-plugin-mihomo-api";
import { useAppData } from "@/providers/app-data-context";
import { useProxiesData, useProxyProvidersData } from "@/hooks/app-data";
import { showNotice } from "@/services/noticeService";
import parseTraffic from "@/utils/parse-traffic";
@@ -48,7 +48,8 @@ const parseExpire = (expire?: number) => {
export const ProviderButton = () => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const { proxyProviders, refreshProxy, refreshProxyProviders } = useAppData();
const { proxyProviders, refreshProxyProviders } = useProxyProvidersData();
const { refreshProxy } = useProxiesData();
const [updating, setUpdating] = useState<Record<string, boolean>>({});
// 检查是否有提供者
@@ -175,8 +176,8 @@ export const ProviderButton = () => {
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(proxyProviders || {})
.sort()
.map(([key, item]) => {
const provider = item;
.map(([key, provider]) => {
if (!provider) return null;
const time = dayjs(provider.updatedAt);
const isUpdating = updating[key];
+2 -2
View File
@@ -39,7 +39,7 @@ import {
selectNodeForGroup,
} from "tauri-plugin-mihomo-api";
import { useAppData } from "@/providers/app-data-context";
import { useProxiesData } from "@/hooks/app-data";
import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds";
interface ProxyChainItem {
@@ -199,7 +199,7 @@ export const ProxyChain = ({
}: ProxyChainProps) => {
const theme = useTheme();
const { t } = useTranslation();
const { proxies } = useAppData();
const { proxies } = useProxiesData();
const [isConnecting, setIsConnecting] = useState(false);
const markUnsavedChanges = useCallback(() => {
onMarkUnsavedChanges?.();
+2 -2
View File
@@ -15,9 +15,9 @@ import { useTranslation } from "react-i18next";
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api";
import { useProxiesData } from "@/hooks/app-data";
import { useProxySelection } from "@/hooks/use-proxy-selection";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-context";
import { updateProxyChainConfigInRuntime } from "@/services/cmds";
import delayManager from "@/services/delay";
@@ -61,7 +61,7 @@ export const ProxyGroups = (props: Props) => {
}>({ open: false, message: "" });
const { verge } = useVerge();
const { proxies: proxiesData } = useAppData();
const { proxies: proxiesData } = useProxiesData();
const groups = proxiesData?.groups;
const availableGroups = useMemo(() => groups ?? [], [groups]);
+4 -20
View File
@@ -1,8 +1,8 @@
import { useEffect, useMemo } from "react";
import useSWR from "swr";
import { useProxiesData } from "@/hooks/app-data";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-context";
import { getRuntimeConfig } from "@/services/cmds";
import delayManager from "@/services/delay";
@@ -33,24 +33,8 @@ interface IProxyItem {
}
// 代理组类型
type ProxyGroup = {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
mptcp: boolean;
smux: boolean;
history: {
time: string;
delay: number;
}[];
now: string;
all: IProxyItem[];
hidden?: boolean;
icon?: string;
testUrl?: string;
provider?: string;
type ProxyGroup = IProxyGroupItem & {
now?: string;
};
export interface IRenderItem {
@@ -99,7 +83,7 @@ export const useRenderList = (
selectedGroup?: string | null,
) => {
// 使用全局数据提供者
const { proxies: proxiesData, refreshProxy } = useAppData();
const { proxies: proxiesData, refreshProxy } = useProxiesData();
const { verge } = useVerge();
const { width } = useWindowWidth();
const [headStates, setHeadState] = useHeadStateNew();
+17 -5
View File
@@ -21,7 +21,7 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { updateRuleProvider } from "tauri-plugin-mihomo-api";
import { useAppData } from "@/providers/app-data-context";
import type { useRuleProvidersData, useRulesData } from "@/hooks/app-data";
import { showNotice } from "@/services/noticeService";
// 辅助组件 - 类型框
@@ -37,10 +37,22 @@ const TypeBox = styled(Box)<{ component?: React.ElementType }>(({ theme }) => ({
lineHeight: 1.25,
}));
export const ProviderButton = () => {
type RuleProvidersHook = ReturnType<typeof useRuleProvidersData>;
type RulesHook = ReturnType<typeof useRulesData>;
interface ProviderButtonProps {
ruleProviders: RuleProvidersHook["ruleProviders"];
refreshRuleProviders: RuleProvidersHook["refreshRuleProviders"];
refreshRules: RulesHook["refreshRules"];
}
export const ProviderButton = ({
ruleProviders,
refreshRuleProviders,
refreshRules,
}: ProviderButtonProps) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const { ruleProviders, refreshRules, refreshRuleProviders } = useAppData();
const [updating, setUpdating] = useState<Record<string, boolean>>({});
// 检查是否有提供者
@@ -163,8 +175,8 @@ export const ProviderButton = () => {
<List sx={{ py: 0, minHeight: 250 }}>
{Object.entries(ruleProviders || {})
.sort()
.map(([key, item]) => {
const provider = item;
.map(([key, provider]) => {
if (!provider) return null;
const time = dayjs(provider.updatedAt);
const isUpdating = updating[key];
+13 -14
View File
@@ -20,15 +20,18 @@ import {
useState,
} from "react";
import { useTranslation } from "react-i18next";
import useSWR, { mutate } from "swr";
import { getBaseConfig } from "tauri-plugin-mihomo-api";
import { mutate } from "swr";
import { BaseDialog, DialogRef, Switch } from "@/components/base";
import { BaseFieldset } from "@/components/base/base-fieldset";
import { TooltipIcon } from "@/components/base/base-tooltip-icon";
import { EditorViewer } from "@/components/profile/editor-viewer";
import {
useClashConfig,
useSystemProxyAddress,
useSystemProxyData,
} from "@/hooks/app-data";
import { useVerge } from "@/hooks/use-verge";
import { useAppData } from "@/providers/app-data-context";
import {
getAutotemProxy,
getNetworkInterfacesInfo,
@@ -92,9 +95,6 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const { verge, patchVerge, mutateVerge } = useVerge();
const [hostOptions, setHostOptions] = useState<string[]>([]);
type SysProxy = Awaited<ReturnType<typeof getSystemProxy>>;
const [sysproxy, setSysproxy] = useState<SysProxy>();
type AutoProxy = Awaited<ReturnType<typeof getAutotemProxy>>;
const [autoproxy, setAutoproxy] = useState<AutoProxy>();
@@ -129,12 +129,8 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
return "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
};
const { data: clashConfig } = useSWR("getClashConfig", getBaseConfig, {
revalidateOnFocus: false,
revalidateIfStale: true,
dedupingInterval: 1000,
errorRetryInterval: 5000,
});
const { clashConfig } = useClashConfig();
const { sysproxy, refreshSysproxy } = useSystemProxyData();
const prevMixedPortRef = useRef(clashConfig?.mixedPort);
@@ -168,7 +164,10 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
updateProxy();
}, [clashConfig?.mixedPort, value.pac]);
const { systemProxyAddress } = useAppData();
const systemProxyAddress = useSystemProxyAddress({
clashConfig,
sysproxy,
});
// 为当前状态计算系统代理地址
const getSystemProxyAddress = useMemo(() => {
@@ -209,7 +208,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
pac_content: pac_file_content ?? DEFAULT_PAC,
proxy_host: proxy_host ?? "127.0.0.1",
});
getSystemProxy().then((p) => setSysproxy(p));
void refreshSysproxy();
getAutotemProxy().then((p) => setAutoproxy(p));
fetchNetworkInterfaces();
},