mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: use React in its intended way (#3963)
* refactor: replace `useEffect` w/ `useLocalStorage` * refactor: replace `useEffect` w/ `useSWR` * refactor: replace `useEffect` and `useSWR`. clean up `useRef` * refactor: use `requestIdleCallback` * refactor: replace `useEffect` w/ `useMemo` * fix: clean up `useEffect` * refactor: replace `useEffect` w/ `useSWR` * refactor: remove unused `useCallback` * refactor: enhance performance and memory management in frontend processes * refactor: improve pre-push script structure and readability --------- Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com> Co-authored-by: Tunglies <tunglies.dev@outlook.com>
This commit is contained in:
@@ -11,18 +11,24 @@ if git diff --cached --name-only | grep -q '^src-tauri/'; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 只在 push 到 origin 并且 origin 指向目标仓库时执行格式检查
|
|
||||||
if [ "$1" = "origin" ] && echo "$2" | grep -Eq 'github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$'; then
|
|
||||||
echo "[pre-push] Detected push to origin (clash-verge-rev/clash-verge-rev)"
|
|
||||||
echo "[pre-push] Running pnpm format:check..."
|
|
||||||
|
|
||||||
|
# Only run format check if the remote exists and is the main repo
|
||||||
|
remote_name="$1"
|
||||||
|
if git remote get-url "$remote_name" >/dev/null 2>&1; then
|
||||||
|
remote_url=$(git remote get-url "$remote_name")
|
||||||
|
if [[ "$remote_url" =~ github\.com[:/]+clash-verge-rev/clash-verge-rev(\.git)?$ ]]; then
|
||||||
|
echo "[pre-push] Detected push to clash-verge-rev/clash-verge-rev ($remote_url)"
|
||||||
|
echo "[pre-push] Running pnpm format:check..."
|
||||||
pnpm format:check
|
pnpm format:check
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "❌ Code format check failed. Please fix formatting before pushing."
|
echo "❌ Code format check failed. Please fix formatting before pushing."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "[pre-push] Not pushing to target repo. Skipping format check."
|
echo "[pre-push] Not pushing to target repo. Skipping format check."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[pre-push] Remote $remote_name does not exist. Skipping format check."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -53,7 +53,8 @@
|
|||||||
|
|
||||||
- 优化 托盘 统一响应
|
- 优化 托盘 统一响应
|
||||||
- 优化 静默启动+自启动轻量模式 运行方式
|
- 优化 静默启动+自启动轻量模式 运行方式
|
||||||
- 升级依赖
|
- 降低前端潜在内存泄漏风险,提升运行时性能
|
||||||
|
- 优化 React 状态、副作用、数据获取、清理等流程。
|
||||||
|
|
||||||
## v2.3.0
|
## v2.3.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useMemo, useState, useEffect } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { DataGrid, GridColDef, GridColumnResizeParams } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef, GridColumnResizeParams } from "@mui/x-data-grid";
|
||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import { truncateStr } from "@/utils/truncate-str";
|
import { truncateStr } from "@/utils/truncate-str";
|
||||||
import parseTraffic from "@/utils/parse-traffic";
|
import parseTraffic from "@/utils/parse-traffic";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
|
import { useLocalStorage } from "foxact/use-local-storage";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
connections: IConnectionsItem[];
|
connections: IConnectionsItem[];
|
||||||
@@ -21,11 +22,13 @@ export const ConnectionTable = (props: Props) => {
|
|||||||
Partial<Record<keyof IConnectionsItem, boolean>>
|
Partial<Record<keyof IConnectionsItem, boolean>>
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
const [columnWidths, setColumnWidths] = useState<Record<string, number>>(
|
const [columnWidths, setColumnWidths] = useLocalStorage<
|
||||||
() => {
|
Record<string, number>
|
||||||
const saved = localStorage.getItem("connection-table-widths");
|
>(
|
||||||
return saved ? JSON.parse(saved) : {};
|
"connection-table-widths",
|
||||||
},
|
// server-side value, this is the default value used by server-side rendering (if any)
|
||||||
|
// Do not omit (otherwise a Suspense boundary will be triggered)
|
||||||
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const [columns] = useState<GridColDef[]>([
|
const [columns] = useState<GridColDef[]>([
|
||||||
@@ -116,14 +119,6 @@ export const ConnectionTable = (props: Props) => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("Saving column widths:", columnWidths);
|
|
||||||
localStorage.setItem(
|
|
||||||
"connection-table-widths",
|
|
||||||
JSON.stringify(columnWidths),
|
|
||||||
);
|
|
||||||
}, [columnWidths]);
|
|
||||||
|
|
||||||
const handleColumnResize = (params: GridColumnResizeParams) => {
|
const handleColumnResize = (params: GridColumnResizeParams) => {
|
||||||
const { colDef, width } = params;
|
const { colDef, width } = params;
|
||||||
console.log("Column resize:", colDef.field, width);
|
console.log("Column resize:", colDef.field, width);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import parseTraffic from "@/utils/parse-traffic";
|
|||||||
import { isDebugEnabled, gc } from "@/services/api";
|
import { isDebugEnabled, gc } from "@/services/api";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
import { useAppData } from "@/providers/app-data-provider";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
interface MemoryUsage {
|
interface MemoryUsage {
|
||||||
inuse: number;
|
inuse: number;
|
||||||
@@ -161,7 +162,6 @@ export const EnhancedTrafficStats = () => {
|
|||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const trafficRef = useRef<EnhancedTrafficGraphRef>(null);
|
const trafficRef = useRef<EnhancedTrafficGraphRef>(null);
|
||||||
const pageVisible = useVisibility();
|
const pageVisible = useVisibility();
|
||||||
const [isDebug, setIsDebug] = useState(false);
|
|
||||||
|
|
||||||
// 使用AppDataProvider
|
// 使用AppDataProvider
|
||||||
const { connections, uptime } = useAppData();
|
const { connections, uptime } = useAppData();
|
||||||
@@ -178,19 +178,16 @@ export const EnhancedTrafficStats = () => {
|
|||||||
// 是否显示流量图表
|
// 是否显示流量图表
|
||||||
const trafficGraph = verge?.traffic_graph ?? true;
|
const trafficGraph = verge?.traffic_graph ?? true;
|
||||||
|
|
||||||
// WebSocket引用
|
|
||||||
const socketRefs = useRef<{
|
|
||||||
traffic: ReturnType<typeof createAuthSockette> | null;
|
|
||||||
memory: ReturnType<typeof createAuthSockette> | null;
|
|
||||||
}>({
|
|
||||||
traffic: null,
|
|
||||||
memory: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查是否支持调试
|
// 检查是否支持调试
|
||||||
useEffect(() => {
|
// TODO: merge this hook with layout-traffic.tsx
|
||||||
isDebugEnabled().then((flag) => setIsDebug(flag));
|
const { data: isDebug } = useSWR(
|
||||||
}, []);
|
`clash-verge-rev-internal://isDebugEnabled`,
|
||||||
|
() => isDebugEnabled(),
|
||||||
|
{
|
||||||
|
// default value before is fetched
|
||||||
|
fallbackData: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 处理流量数据更新 - 使用节流控制更新频率
|
// 处理流量数据更新 - 使用节流控制更新频率
|
||||||
const handleTrafficUpdate = useCallback((event: MessageEvent) => {
|
const handleTrafficUpdate = useCallback((event: MessageEvent) => {
|
||||||
@@ -260,14 +257,23 @@ export const EnhancedTrafficStats = () => {
|
|||||||
const { server, secret = "" } = clashInfo;
|
const { server, secret = "" } = clashInfo;
|
||||||
if (!server) return;
|
if (!server) return;
|
||||||
|
|
||||||
|
// WebSocket 引用
|
||||||
|
let sockets: {
|
||||||
|
traffic: ReturnType<typeof createAuthSockette> | null;
|
||||||
|
memory: ReturnType<typeof createAuthSockette> | null;
|
||||||
|
} = {
|
||||||
|
traffic: null,
|
||||||
|
memory: null,
|
||||||
|
};
|
||||||
|
|
||||||
// 清理现有连接的函数
|
// 清理现有连接的函数
|
||||||
const cleanupSockets = () => {
|
const cleanupSockets = () => {
|
||||||
Object.values(socketRefs.current).forEach((socket) => {
|
Object.values(sockets).forEach((socket) => {
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socketRefs.current = { traffic: null, memory: null };
|
sockets = { traffic: null, memory: null };
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭现有连接
|
// 关闭现有连接
|
||||||
@@ -277,10 +283,7 @@ export const EnhancedTrafficStats = () => {
|
|||||||
console.log(
|
console.log(
|
||||||
`[Traffic][${EnhancedTrafficStats.name}] 正在连接: ${server}/traffic`,
|
`[Traffic][${EnhancedTrafficStats.name}] 正在连接: ${server}/traffic`,
|
||||||
);
|
);
|
||||||
socketRefs.current.traffic = createAuthSockette(
|
sockets.traffic = createAuthSockette(`${server}/traffic`, secret, {
|
||||||
`${server}/traffic`,
|
|
||||||
secret,
|
|
||||||
{
|
|
||||||
onmessage: handleTrafficUpdate,
|
onmessage: handleTrafficUpdate,
|
||||||
onopen: (event) => {
|
onopen: (event) => {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -308,13 +311,12 @@ export const EnhancedTrafficStats = () => {
|
|||||||
setStats((prev) => ({ ...prev, traffic: { up: 0, down: 0 } }));
|
setStats((prev) => ({ ...prev, traffic: { up: 0, down: 0 } }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Memory][${EnhancedTrafficStats.name}] 正在连接: ${server}/memory`,
|
`[Memory][${EnhancedTrafficStats.name}] 正在连接: ${server}/memory`,
|
||||||
);
|
);
|
||||||
socketRefs.current.memory = createAuthSockette(`${server}/memory`, secret, {
|
sockets.memory = createAuthSockette(`${server}/memory`, secret, {
|
||||||
onmessage: handleMemoryUpdate,
|
onmessage: handleMemoryUpdate,
|
||||||
onopen: (event) => {
|
onopen: (event) => {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -353,18 +355,6 @@ export const EnhancedTrafficStats = () => {
|
|||||||
return cleanupSockets;
|
return cleanupSockets;
|
||||||
}, [clashInfo, pageVisible, handleTrafficUpdate, handleMemoryUpdate]);
|
}, [clashInfo, pageVisible, handleTrafficUpdate, handleMemoryUpdate]);
|
||||||
|
|
||||||
// 组件卸载时清理所有定时器/引用
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
try {
|
|
||||||
Object.values(socketRefs.current).forEach((socket) => {
|
|
||||||
if (socket) socket.close();
|
|
||||||
});
|
|
||||||
socketRefs.current = { traffic: null, memory: null };
|
|
||||||
} catch {}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 执行垃圾回收
|
// 执行垃圾回收
|
||||||
const handleGarbageCollection = useCallback(async () => {
|
const handleGarbageCollection = useCallback(async () => {
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import useSWRSubscription from "swr/subscription";
|
|||||||
import { createAuthSockette } from "@/utils/websocket";
|
import { createAuthSockette } from "@/utils/websocket";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { isDebugEnabled, gc } from "@/services/api";
|
import { isDebugEnabled, gc } from "@/services/api";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
interface MemoryUsage {
|
interface MemoryUsage {
|
||||||
inuse: number;
|
inuse: number;
|
||||||
@@ -31,12 +32,15 @@ export const LayoutTraffic = () => {
|
|||||||
|
|
||||||
const trafficRef = useRef<TrafficRef>(null);
|
const trafficRef = useRef<TrafficRef>(null);
|
||||||
const pageVisible = useVisibility();
|
const pageVisible = useVisibility();
|
||||||
const [isDebug, setIsDebug] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { data: isDebug } = useSWR(
|
||||||
isDebugEnabled().then((flag) => setIsDebug(flag));
|
"clash-verge-rev-internal://isDebugEnabled",
|
||||||
return () => {};
|
() => isDebugEnabled(),
|
||||||
}, [isDebug]);
|
{
|
||||||
|
// default value before is fetched
|
||||||
|
fallbackData: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const { data: traffic = { up: 0, down: 0 } } = useSWRSubscription<
|
const { data: traffic = { up: 0, down: 0 } } = useSWRSubscription<
|
||||||
ITrafficItem,
|
ITrafficItem,
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ import MonacoEditor from "react-monaco-editor";
|
|||||||
import { useThemeMode } from "@/services/states";
|
import { useThemeMode } from "@/services/states";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
import {
|
||||||
|
requestIdleCallback,
|
||||||
|
cancelIdleCallback,
|
||||||
|
} from "foxact/request-idle-callback";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
proxiesUid: string;
|
proxiesUid: string;
|
||||||
@@ -195,11 +199,11 @@ export const GroupsEditorViewer = (props: Props) => {
|
|||||||
// 防止异常导致UI卡死
|
// 防止异常导致UI卡死
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (window.requestIdleCallback) {
|
|
||||||
window.requestIdleCallback(serialize);
|
const handle = requestIdleCallback(serialize);
|
||||||
} else {
|
return () => {
|
||||||
setTimeout(serialize, 0);
|
cancelIdleCallback(handle);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}, [prependSeq, appendSeq, deleteSeq]);
|
}, [prependSeq, appendSeq, deleteSeq]);
|
||||||
|
|
||||||
|
|||||||
@@ -480,8 +480,11 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
}
|
}
|
||||||
}, [handleWheel]);
|
}, [handleWheel]);
|
||||||
|
|
||||||
|
// 监听窗口大小变化
|
||||||
|
// layout effect runs before paint
|
||||||
|
useEffect(() => {
|
||||||
// 添加窗口大小变化监听和最大高度计算
|
// 添加窗口大小变化监听和最大高度计算
|
||||||
const updateMaxHeight = useCallback(() => {
|
const updateMaxHeight = () => {
|
||||||
if (!alphabetSelectorRef.current) return;
|
if (!alphabetSelectorRef.current) return;
|
||||||
|
|
||||||
const windowHeight = window.innerHeight;
|
const windowHeight = window.innerHeight;
|
||||||
@@ -495,16 +498,16 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
alphabetSelectorRef.current.style.top = `calc(48% + ${offsetPercentage}vh)`;
|
alphabetSelectorRef.current.style.top = `calc(48% + ${offsetPercentage}vh)`;
|
||||||
|
|
||||||
setMaxHeight(`${availableHeight}px`);
|
setMaxHeight(`${availableHeight}px`);
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
// 监听窗口大小变化
|
|
||||||
useEffect(() => {
|
|
||||||
updateMaxHeight();
|
updateMaxHeight();
|
||||||
|
|
||||||
window.addEventListener("resize", updateMaxHeight);
|
window.addEventListener("resize", updateMaxHeight);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", updateMaxHeight);
|
window.removeEventListener("resize", updateMaxHeight);
|
||||||
};
|
};
|
||||||
}, [updateMaxHeight]);
|
}, []);
|
||||||
|
|
||||||
if (mode === "direct") {
|
if (mode === "direct") {
|
||||||
return <BaseEmpty text={t("clash_mode_direct")} />;
|
return <BaseEmpty text={t("clash_mode_direct")} />;
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ export const useRenderList = (mode: string) => {
|
|||||||
(mode === "rule" && !groups.length) ||
|
(mode === "rule" && !groups.length) ||
|
||||||
(mode === "global" && proxies.length < 2)
|
(mode === "global" && proxies.length < 2)
|
||||||
) {
|
) {
|
||||||
setTimeout(() => refreshProxy(), 500);
|
const handle = setTimeout(() => refreshProxy(), 500);
|
||||||
|
return () => clearTimeout(handle);
|
||||||
}
|
}
|
||||||
}, [proxiesData, mode, refreshProxy]);
|
}, [proxiesData, mode, refreshProxy]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useState,
|
useState,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BaseDialog, DialogRef } from "@/components/base";
|
import { BaseDialog, DialogRef } from "@/components/base";
|
||||||
@@ -30,7 +30,6 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([]);
|
const [backupFiles, setBackupFiles] = useState<BackupFile[]>([]);
|
||||||
const [dataSource, setDataSource] = useState<BackupFile[]>([]);
|
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
|
||||||
@@ -91,14 +90,14 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1));
|
.sort((a, b) => (a.backup_time.isAfter(b.backup_time) ? -1 : 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const dataSource = useMemo<BackupFile[]>(
|
||||||
setDataSource(
|
() =>
|
||||||
backupFiles.slice(
|
backupFiles.slice(
|
||||||
page * DEFAULT_ROWS_PER_PAGE,
|
page * DEFAULT_ROWS_PER_PAGE,
|
||||||
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE,
|
page * DEFAULT_ROWS_PER_PAGE + DEFAULT_ROWS_PER_PAGE,
|
||||||
),
|
),
|
||||||
|
[backupFiles, page],
|
||||||
);
|
);
|
||||||
}, [page, backupFiles]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
@@ -116,18 +115,10 @@ export const BackupViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
<Paper elevation={2} sx={{ padding: 2 }}>
|
<Paper elevation={2} sx={{ padding: 2 }}>
|
||||||
<BackupConfigViewer
|
<BackupConfigViewer
|
||||||
setLoading={setIsLoading}
|
setLoading={setIsLoading}
|
||||||
onBackupSuccess={async () => {
|
onBackupSuccess={fetchAndSetBackupFiles}
|
||||||
fetchAndSetBackupFiles();
|
onSaveSuccess={fetchAndSetBackupFiles}
|
||||||
}}
|
onRefresh={fetchAndSetBackupFiles}
|
||||||
onSaveSuccess={async () => {
|
onInit={fetchAndSetBackupFiles}
|
||||||
fetchAndSetBackupFiles();
|
|
||||||
}}
|
|
||||||
onRefresh={async () => {
|
|
||||||
fetchAndSetBackupFiles();
|
|
||||||
}}
|
|
||||||
onInit={async () => {
|
|
||||||
fetchAndSetBackupFiles();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Divider sx={{ marginY: 2 }} />
|
<Divider sx={{ marginY: 2 }} />
|
||||||
<BackupTableViewer
|
<BackupTableViewer
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ import { alpha, Box, Button, IconButton } from "@mui/material";
|
|||||||
import { ContentCopyRounded } from "@mui/icons-material";
|
import { ContentCopyRounded } from "@mui/icons-material";
|
||||||
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [networkInterfaces, setNetworkInterfaces] = useState<
|
|
||||||
INetworkInterface[]
|
|
||||||
>([]);
|
|
||||||
const [isV4, setIsV4] = useState(true);
|
const [isV4, setIsV4] = useState(true);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -22,12 +20,13 @@ export const NetworkInterfaceViewer = forwardRef<DialogRef>((props, ref) => {
|
|||||||
close: () => setOpen(false),
|
close: () => setOpen(false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
const { data: networkInterfaces } = useSWR(
|
||||||
if (!open) return;
|
"clash-verge-rev-internal://network-interfaces",
|
||||||
getNetworkInterfacesInfo().then((res) => {
|
getNetworkInterfacesInfo,
|
||||||
setNetworkInterfaces(res);
|
{
|
||||||
});
|
fallbackData: [], // default data before fetch
|
||||||
}, [open]);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
|
|||||||
Reference in New Issue
Block a user