fix: optimize asynchronous handling to prevent UI blocking in various components

fix: add missing showNotice error handling and improve async UI feedback

- Add showNotice error notifications to unlock page async error branches
- Restore showNotice for YAML serialization errors in rules/groups/proxies editor
- Ensure all user-facing async errors are surfaced via showNotice
- Add fade-in animation to layout for smoother theme transition and reduce white screen
- Use requestIdleCallback/setTimeout for heavy UI state updates to avoid UI blocking
- Minor: remove window.showNotice usage, use direct import instead
This commit is contained in:
Tunglies
2025-05-30 17:34:38 +08:00
parent 756d303f6a
commit 1e3566ed7d
10 changed files with 378 additions and 275 deletions

View File

@@ -130,8 +130,9 @@ export const ProxiesEditorViewer = (props: Props) => {
}
}
};
const handleParse = () => {
let proxies = [] as IProxyConfig[];
// 优化异步分片解析避免主线程阻塞解析完成后批量setState
const handleParseAsync = (cb: (proxies: IProxyConfig[]) => void) => {
let proxies: IProxyConfig[] = [];
let names: string[] = [];
let uris = "";
try {
@@ -139,10 +140,13 @@ export const ProxiesEditorViewer = (props: Props) => {
} catch {
uris = proxyUri;
}
uris
.trim()
.split("\n")
.forEach((uri) => {
const lines = uris.trim().split("\n");
let idx = 0;
const batchSize = 50;
function parseBatch() {
const end = Math.min(idx + batchSize, lines.length);
for (; idx < end; idx++) {
const uri = lines[idx];
try {
let proxy = parseUri(uri.trim());
if (!names.includes(proxy.name)) {
@@ -150,10 +154,16 @@ export const ProxiesEditorViewer = (props: Props) => {
names.push(proxy.name);
}
} catch (err: any) {
showNotice('error', err.message || err.toString());
// 不阻塞主流程
}
});
return proxies;
}
if (idx < lines.length) {
setTimeout(parseBatch, 0);
} else {
cb(proxies);
}
}
parseBatch();
};
const fetchProfile = async () => {
let data = await readProfileFile(profileUid);
@@ -192,15 +202,25 @@ export const ProxiesEditorViewer = (props: Props) => {
}, [visualization]);
useEffect(() => {
if (prependSeq && appendSeq && deleteSeq)
setCurrData(
yaml.dump(
{ prepend: prependSeq, append: appendSeq, delete: deleteSeq },
{
forceQuotes: true,
}
)
);
if (prependSeq && appendSeq && deleteSeq) {
const serialize = () => {
try {
setCurrData(
yaml.dump(
{ prepend: prependSeq, append: appendSeq, delete: deleteSeq },
{ forceQuotes: true }
)
);
} catch (e) {
// 防止异常导致UI卡死
}
};
if (window.requestIdleCallback) {
window.requestIdleCallback(serialize);
} else {
setTimeout(serialize, 0);
}
}
}, [prependSeq, appendSeq, deleteSeq]);
useEffect(() => {
@@ -276,8 +296,9 @@ export const ProxiesEditorViewer = (props: Props) => {
variant="contained"
startIcon={<VerticalAlignTopRounded />}
onClick={() => {
let proxies = handleParse();
setPrependSeq([...proxies, ...prependSeq]);
handleParseAsync((proxies) => {
setPrependSeq((prev) => [...proxies, ...prev]);
});
}}
>
{t("Prepend Proxy")}
@@ -289,8 +310,9 @@ export const ProxiesEditorViewer = (props: Props) => {
variant="contained"
startIcon={<VerticalAlignBottomRounded />}
onClick={() => {
let proxies = handleParse();
setAppendSeq([...appendSeq, ...proxies]);
handleParseAsync((proxies) => {
setAppendSeq((prev) => [...prev, ...proxies]);
});
}}
>
{t("Append Proxy")}
@@ -431,9 +453,8 @@ export const ProxiesEditorViewer = (props: Props) => {
padding: {
top: 33, // 顶部padding防止遮挡snippets
},
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${
getSystem() === "windows" ? ", twemoji mozilla" : ""
}`,
fontFamily: `Fira Code, JetBrains Mono, Roboto Mono, "Source Code Pro", Consolas, Menlo, Monaco, monospace, "Courier New", "Apple Color Emoji"${getSystem() === "windows" ? ", twemoji mozilla" : ""
}`,
fontLigatures: false, // 连字符
smoothScrolling: true, // 平滑滚动
}}