fix: optimize connections page performance and state management

This commit is contained in:
xmk23333 2025-12-08 16:53:00 +08:00
parent 7619b4d3e5
commit 51720296cc
3 changed files with 56 additions and 43 deletions

View File

@ -9,6 +9,7 @@ export const defaultConfig: IAppConfig = {
appTheme: 'system', appTheme: 'system',
useWindowFrame: false, useWindowFrame: false,
proxyInTray: true, proxyInTray: true,
showCurrentProxyInTray: false,
disableTrayIconColor: false, disableTrayIconColor: false,
maxLogDays: 7, maxLogDays: 7,
proxyCols: 'auto', proxyCols: 'auto',

View File

@ -1,6 +1,6 @@
import BasePage from '@renderer/components/base/base-page' import BasePage from '@renderer/components/base/base-page'
import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc' import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc'
import { Key, useCallback, useEffect, useMemo, useState } from 'react' import { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react' import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@heroui/react'
import { calcTraffic } from '@renderer/utils/calc' import { calcTraffic } from '@renderer/utils/calc'
import ConnectionItem from '@renderer/components/connections/connection-item' import ConnectionItem from '@renderer/components/connections/connection-item'
@ -48,6 +48,13 @@ const Connections: React.FC = () => {
const [viewMode, setViewMode] = useState<'list' | 'table'>(connectionViewMode) const [viewMode, setViewMode] = useState<'list' | 'table'>(connectionViewMode)
const [visibleColumns, setVisibleColumns] = useState<Set<string>>(new Set(connectionTableColumns)) const [visibleColumns, setVisibleColumns] = useState<Set<string>>(new Set(connectionTableColumns))
const activeConnectionsRef = useRef(activeConnections)
const allConnectionsRef = useRef(allConnections)
useEffect(() => {
activeConnectionsRef.current = activeConnections
allConnectionsRef.current = allConnections
}, [activeConnections, allConnections])
const handleColumnWidthChange = useCallback(async (widths: Record<string, number>) => { const handleColumnWidthChange = useCallback(async (widths: Record<string, number>) => {
await patchAppConfig({ connectionTableColumnWidths: widths }) await patchAppConfig({ connectionTableColumnWidths: widths })
}, [patchAppConfig]) }, [patchAppConfig])
@ -98,71 +105,80 @@ const Connections: React.FC = () => {
const closeAllConnections = useCallback((): void => { const closeAllConnections = useCallback((): void => {
tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection() tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection()
}, [tab, closedConnections]) }, [tab])
const closeConnection = useCallback((id: string): void => { const closeConnection = useCallback((id: string): void => {
tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id) tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id)
}, [tab]) }, [tab])
const trashAllClosedConnection = (): void => { const trashAllClosedConnection = (): void => {
const trashIds = closedConnections.map((conn) => conn.id) setClosedConnections((closedConns) => {
setAllConnections((allConns) => allConns.filter((conn) => !trashIds.includes(conn.id))) const trashIds = new Set(closedConns.map((conn) => conn.id))
setClosedConnections([]) setAllConnections((allConns) => {
const filtered = allConns.filter((conn) => !trashIds.has(conn.id))
cachedConnections = allConnections cachedConnections = filtered
return filtered
})
return []
})
} }
const trashClosedConnection = (id: string): void => { const trashClosedConnection = (id: string): void => {
setAllConnections((allConns) => allConns.filter((conn) => conn.id != id)) setAllConnections((allConns) => {
setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id != id)) const filtered = allConns.filter((conn) => conn.id !== id)
cachedConnections = filtered
cachedConnections = allConnections return filtered
})
setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id !== id))
} }
useEffect(() => { useEffect(() => {
if (isPaused) return const handler = (_e: unknown, info: IMihomoConnectionsInfo): void => {
window.electron.ipcRenderer.on('mihomoConnections', (_e, info: IMihomoConnectionsInfo) => {
setConnectionsInfo(info) setConnectionsInfo(info)
if (!info.connections) return if (!info.connections) return
const allConns = unionWith(activeConnections, allConnections, (a, b) => a.id === b.id) const allConns = unionWith(
activeConnectionsRef.current,
allConnectionsRef.current,
(a, b) => a.id === b.id
)
const prevConnMap = new Map(activeConnectionsRef.current.map((c) => [c.id, c]))
const activeConns = info.connections.map((conn) => { const activeConns = info.connections.map((conn) => {
const preConn = activeConnections.find((c) => c.id === conn.id) const preConn = prevConnMap.get(conn.id)
const downloadSpeed = preConn ? conn.download - preConn.download : 0
const uploadSpeed = preConn ? conn.upload - preConn.upload : 0
return { return {
...conn, ...conn,
isActive: true, isActive: true,
downloadSpeed: downloadSpeed, downloadSpeed: preConn ? conn.download - preConn.download : 0,
uploadSpeed: uploadSpeed uploadSpeed: preConn ? conn.upload - preConn.upload : 0
} }
}) })
const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map( const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map(
(conn) => { (conn) => ({
return { ...conn,
...conn, isActive: false,
isActive: false, downloadSpeed: 0,
downloadSpeed: 0, uploadSpeed: 0
uploadSpeed: 0 })
}
}
) )
setActiveConnections(activeConns) setActiveConnections(activeConns)
setClosedConnections(closedConns) setClosedConnections(closedConns)
setAllConnections(allConns.slice(-(activeConns.length + 200))) setAllConnections(allConns.slice(-(activeConns.length + 200)))
cachedConnections = allConns
}
cachedConnections = allConnections if (!isPaused) {
}) window.electron.ipcRenderer.on('mihomoConnections', handler)
}
return (): void => { return (): void => {
window.electron.ipcRenderer.removeAllListeners('mihomoConnections') window.electron.ipcRenderer.removeAllListeners('mihomoConnections')
} }
}, [allConnections, activeConnections, closedConnections, isPaused]) }, [isPaused])
const togglePause = () => { const togglePause = useCallback(() => {
setIsPaused(!isPaused) setIsPaused((prev) => !prev)
} }, [])
return ( return (
<BasePage <BasePage
@ -182,7 +198,7 @@ const Connections: React.FC = () => {
color="primary" color="primary"
variant="flat" variant="flat"
showOutline={false} showOutline={false}
content={`${filteredConnections.length}`} content={filteredConnections.length}
> >
<Button <Button
className="app-nodrag ml-1" className="app-nodrag ml-1"
@ -237,7 +253,7 @@ const Connections: React.FC = () => {
<div className="flex p-2 gap-2"> <div className="flex p-2 gap-2">
<Tabs <Tabs
size="sm" size="sm"
color={`${tab === 'active' ? 'primary' : 'danger'}`} color={tab === 'active' ? 'primary' : 'danger'}
selectedKey={tab} selectedKey={tab}
variant="underlined" variant="underlined"
className="w-fit h-[32px]" className="w-fit h-[32px]"
@ -249,7 +265,7 @@ const Connections: React.FC = () => {
key="active" key="active"
title={ title={
<Badge <Badge
color={`${tab === 'active' ? 'primary' : 'default'}`} color={tab === 'active' ? 'primary' : 'default'}
size="sm" size="sm"
shape="circle" shape="circle"
variant="flat" variant="flat"
@ -264,7 +280,7 @@ const Connections: React.FC = () => {
key="closed" key="closed"
title={ title={
<Badge <Badge
color={`${tab === 'closed' ? 'danger' : 'default'}`} color={tab === 'closed' ? 'danger' : 'default'}
size="sm" size="sm"
shape="circle" shape="circle"
variant="flat" variant="flat"
@ -339,7 +355,7 @@ const Connections: React.FC = () => {
size="sm" size="sm"
className="w-[180px] min-w-[131px]" className="w-[180px] min-w-[131px]"
aria-label={t('connections.orderBy')} aria-label={t('connections.orderBy')}
selectedKeys={new Set([connectionOrderBy])} selectedKeys={[connectionOrderBy]}
disallowEmptySelection={true} disallowEmptySelection={true}
onSelectionChange={async (v) => { onSelectionChange={async (v) => {
await patchAppConfig({ await patchAppConfig({
@ -362,7 +378,7 @@ const Connections: React.FC = () => {
size="sm" size="sm"
isIconOnly isIconOnly
className="bg-content2" className="bg-content2"
onPress={async () => { onPress={() => {
patchAppConfig({ patchAppConfig({
connectionDirection: connectionDirection === 'asc' ? 'desc' : 'asc' connectionDirection: connectionDirection === 'asc' ? 'desc' : 'asc'
}) })

View File

@ -44,10 +44,6 @@ const Mihomo: React.FC = () => {
smartCollectorSize = 100, smartCollectorSize = 100,
maxLogDays = 7, maxLogDays = 7,
sysProxy, sysProxy,
disableLoopbackDetector,
disableEmbedCA,
disableSystemCA,
skipSafePathCheck,
showMixedPort, showMixedPort,
enableMixedPort = true, enableMixedPort = true,
showSocksPort, showSocksPort,