From 2b534e0d51ec087f8f1b626dca166beffa98da0e Mon Sep 17 00:00:00 2001 From: wonfen Date: Thu, 20 Feb 2025 14:21:55 +0800 Subject: [PATCH] refactor: Optimize proxy rendering and layout calculation --- src/components/proxy/proxy-groups.tsx | 4 +- src/components/proxy/use-render-list.ts | 324 +++++++----------------- src/pages/profiles.tsx | 4 +- 3 files changed, 89 insertions(+), 243 deletions(-) diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index 57fdec40..b4e64599 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -473,7 +473,7 @@ export const ProxyGroups = (props: Props) => {
{ scrollerRef.current = ref as Element; }} components={{ - Footer: () =>
, + Footer: () =>
, }} itemContent={(index) => ( { + if (configCol > 0 && configCol < 6) return configCol; -// 缓存计算结果 -const groupCache = new WeakMap>(); -// 用于追踪缓存的key -const cacheKeys = new Set(); + if (width > 1920) return 5; + if (width > 1450) return 4; + if (width > 1024) return 3; + if (width > 900) return 2; + if (width >= 600) return 2; + return 1; +}; + +// 优化分组逻辑 +const groupProxies = (list: T[], size: number): T[][] => { + return list.reduce((acc, item) => { + const lastGroup = acc[acc.length - 1]; + if (!lastGroup || lastGroup.length >= size) { + acc.push([item]); + } else { + lastGroup.push(item); + } + return acc; + }, [] as T[][]); +}; export const useRenderList = (mode: string) => { - // 添加用户交互标记 - const isUserInteracting = useRef(false); - const interactionTimer = useRef(null); - // 添加上一次有效的数据缓存 - const [lastValidData, setLastValidData] = useState(null); - // 添加刷新锁 - const refreshLock = useRef(false); - const lastRenderList = useRef([]); - - // 组件卸载时清理 - useEffect(() => { - return () => { - if (interactionTimer.current) { - clearTimeout(interactionTimer.current); - } - refreshLock.current = false; - isUserInteracting.current = false; - // 清理 WeakMap 缓存 - cacheKeys.forEach((key) => { - groupCache.delete(key); - }); - cacheKeys.clear(); - }; - }, []); - - // 优化数据获取函数 - const fetchProxies = useCallback(async () => { - try { - if (isUserInteracting.current || refreshLock.current) { - return lastValidData; - } - const data = await getProxies(); - - // 预处理和缓存组数据 - if (data && !groupCache.has(data)) { - const groupMap = new Map(); - data.groups.forEach((group) => { - groupMap.set(group.name, group); - }); - groupCache.set(data, groupMap); - cacheKeys.add(data); - } - - setLastValidData(data); - return data; - } catch (error) { - if (lastValidData) return lastValidData; - throw error; - } - }, [lastValidData]); - const { data: proxiesData, mutate: mutateProxies } = useSWR( "getProxies", - fetchProxies, + getProxies, { refreshInterval: 2000, - dedupingInterval: 1000, revalidateOnFocus: false, - keepPreviousData: true, - onSuccess: (data) => { - if (!data || isUserInteracting.current) return; - - if (proxiesData) { - try { - const groupMap = groupCache.get(proxiesData); - if (!groupMap) return; - - const needUpdate = data.groups.some((group: IProxyGroupItem) => { - const oldGroup = groupMap.get(group.name); - if (!oldGroup) return true; - - return ( - oldGroup.now !== group.now || - oldGroup.type !== group.type || - JSON.stringify(oldGroup.all) !== JSON.stringify(group.all) - ); - }); - - if (!needUpdate) { - return; - } - } catch (e) { - console.error("Data comparison error:", e); - return; - } - } - }, + revalidateOnReconnect: true, }, ); - // 优化mutateProxies包装函数 - const wrappedMutateProxies = useCallback(async () => { - if (interactionTimer.current) { - clearTimeout(interactionTimer.current); - } - - try { - // 立即更新本地状态以响应UI - if (proxiesData) { - const currentGroup = proxiesData.groups.find( - (g) => g.now !== undefined, - ); - if (currentGroup) { - const optimisticData = { ...proxiesData }; - setLastValidData(optimisticData); - } - } - - // 执行实际的更新并等待结果 - const result = await mutateProxies(); - - // 更新最新数据 - if (result) { - setLastValidData(result); - } - - return result; - } catch (error) { - console.error("Failed to update proxies:", error); - // 发生错误时恢复到之前的状态 - if (proxiesData) { - setLastValidData(proxiesData); - } - throw error; - } finally { - // 重置状态 - isUserInteracting.current = false; - refreshLock.current = false; - if (interactionTimer.current) { - clearTimeout(interactionTimer.current); - interactionTimer.current = null; - } - } - }, [proxiesData, mutateProxies]); - - // 确保初始数据加载后更新lastValidData - useEffect(() => { - if (proxiesData && !lastValidData) { - setLastValidData(proxiesData); - } - }, [proxiesData]); - const { verge } = useVerge(); const { width } = useWindowWidth(); - - const col = useMemo(() => { - const baseCol = Math.floor(verge?.proxy_layout_column || 6); - if (baseCol >= 6 || baseCol <= 0) { - if (width > 1450) return 4; - if (width > 1024) return 3; - if (width > 900) return 2; - if (width >= 600) return 2; - return 1; - } - return baseCol; - }, [verge?.proxy_layout_column, width]); - const [headStates, setHeadState] = useHeadStateNew(); - // 优化初始数据加载 + // 计算列数 + const col = useMemo( + () => calculateColumns(width, verge?.proxy_layout_column || 6), + [width, verge?.proxy_layout_column], + ); + + // 确保代理数据加载 useEffect(() => { if (!proxiesData) return; const { groups, proxies } = proxiesData; @@ -201,26 +80,31 @@ export const useRenderList = (mode: string) => { (mode === "rule" && !groups.length) || (mode === "global" && proxies.length < 2) ) { - const timer = setTimeout(() => mutateProxies(), 500); - return () => clearTimeout(timer); + setTimeout(() => mutateProxies(), 500); } }, [proxiesData, mode, mutateProxies]); - // 优化渲染列表计算 - const renderList = useMemo(() => { - const currentData = proxiesData || lastValidData; - if (!currentData) return lastRenderList.current; + // 处理渲染列表 + const renderList: IRenderItem[] = useMemo(() => { + if (!proxiesData) return []; const useRule = mode === "rule" || mode === "script"; const renderGroups = - (useRule && currentData.groups.length - ? currentData.groups - : [currentData.global!]) || []; + useRule && proxiesData.groups.length + ? proxiesData.groups + : [proxiesData.global!]; - const newList = renderGroups.flatMap((group: IProxyGroupItem) => { + const retList = renderGroups.flatMap((group) => { const headState = headStates[group.name] || DEFAULT_STATE; const ret: IRenderItem[] = [ - { type: 0, key: group.name, group, headState }, + { + type: 0, + key: group.name, + group, + headState, + icon: group.icon, + testUrl: group.testUrl, + }, ]; if (headState?.open || !useRule) { @@ -231,94 +115,56 @@ export const useRenderList = (mode: string) => { headState.sortType, ); - ret.push({ type: 1, key: `head-${group.name}`, group, headState }); + ret.push({ + type: 1, + key: `head-${group.name}`, + group, + headState, + }); if (!proxies.length) { - ret.push({ type: 3, key: `empty-${group.name}`, group, headState }); - return ret; - } - - if (col > 1) { + ret.push({ + type: 3, + key: `empty-${group.name}`, + group, + headState, + }); + } else if (col > 1) { return ret.concat( - groupList(proxies, col).map((proxyCol) => ({ + groupProxies(proxies, col).map((proxyCol) => ({ type: 4, key: `col-${group.name}-${proxyCol[0].name}`, group, headState, col, proxyCol, + provider: proxyCol[0].provider, + })), + ); + } else { + return ret.concat( + proxies.map((proxy) => ({ + type: 2, + key: `${group.name}-${proxy!.name}`, + group, + proxy, + headState, + provider: proxy.provider, })), ); } - - return ret.concat( - proxies.map((proxy) => ({ - type: 2, - key: `${group.name}-${proxy!.name}`, - group, - proxy, - headState, - })), - ); } return ret; }); - const filteredList = !useRule - ? newList.slice(1) - : newList.filter((item) => !item.group.hidden); - - lastRenderList.current = filteredList; - return filteredList; - }, [headStates, proxiesData, lastValidData, mode, col]); - - // 添加滚动处理 - useEffect(() => { - const handleScroll = () => { - if (!isUserInteracting.current) { - isUserInteracting.current = true; - - // 清除之前的定时器 - if (interactionTimer.current) { - clearTimeout(interactionTimer.current); - } - - // 设置新的定时器,在滚动停止后恢复刷新 - interactionTimer.current = window.setTimeout(() => { - isUserInteracting.current = false; - // 手动触发一次更新 - wrappedMutateProxies(); - }, 1000) as unknown as number; - } - }; - - window.addEventListener("scroll", handleScroll, { passive: true }); - return () => { - window.removeEventListener("scroll", handleScroll); - if (interactionTimer.current) { - clearTimeout(interactionTimer.current); - } - }; - }, [wrappedMutateProxies]); + if (!useRule) return retList.slice(1); + return retList.filter((item) => !item.group.hidden); + }, [headStates, proxiesData, mode, col]); return { renderList, - onProxies: wrappedMutateProxies, + onProxies: mutateProxies, onHeadState: setHeadState, + currentColumns: col, }; }; - -function groupList(list: T[], size: number): T[][] { - return list.reduce((p, n) => { - if (!p.length) return [[n]]; - - const i = p.length - 1; - if (p[i].length < size) { - p[i].push(n); - return p; - } - - p.push([n]); - return p; - }, [] as T[][]); -} diff --git a/src/pages/profiles.tsx b/src/pages/profiles.tsx index f2b507b3..6fcb883e 100644 --- a/src/pages/profiles.tsx +++ b/src/pages/profiles.tsx @@ -377,7 +377,7 @@ const ProfilePage = () => { sx={{ pl: "10px", pr: "10px", - height: "94%", + height: "calc(100% - 48px)", overflowY: "auto", }} > @@ -416,7 +416,7 @@ const ProfilePage = () => { flexItem sx={{ width: `calc(100% - 32px)`, borderColor: dividercolor }} > - +