mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 09:55:56 +08:00
fix: PathTable — 环境变量展开限流20并发、消除useEffect双重触发、类型断言改为常量
- expand useEffect 增加 .slice(0, 20) 批次限制,避免大量路径时并发过高 - validatedRef / expandedRef 替代 validationCache.has / expandedCache.has 过滤, 从 useEffect 依赖数组中移除缓存 state,消除双重触发 - ValidationState 类型提升到模块层级,新增 DEFAULT_VALIDATION_STATE 常量 替代硬编码类型断言 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||||
import { useAppStore } from '@/store/app-store';
|
import { useAppStore } from '@/store/app-store';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
|
|
||||||
@@ -11,6 +11,9 @@ interface PathRow {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidationState = 'valid' | 'invalid' | 'unknown';
|
||||||
|
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';
|
||||||
|
|
||||||
export function PathTable({ tabId }: PathTableProps) {
|
export function PathTable({ tabId }: PathTableProps) {
|
||||||
const sysPaths = useAppStore((s) => s.sysPaths);
|
const sysPaths = useAppStore((s) => s.sysPaths);
|
||||||
const userPaths = useAppStore((s) => s.userPaths);
|
const userPaths = useAppStore((s) => s.userPaths);
|
||||||
@@ -22,12 +25,14 @@ export function PathTable({ tabId }: PathTableProps) {
|
|||||||
const paths = tabId === 'system' ? sysPaths : userPaths;
|
const paths = tabId === 'system' ? sysPaths : userPaths;
|
||||||
const isActive = activeTab === tabId;
|
const isActive = activeTab === tabId;
|
||||||
|
|
||||||
type ValidationState = 'valid' | 'invalid' | 'unknown';
|
|
||||||
// 本次会话中已验证过的路径缓存(key=path, value=ValidationState)
|
// 本次会话中已验证过的路径缓存(key=path, value=ValidationState)
|
||||||
const [validationCache, setValidationCache] = useState<Map<string, ValidationState>>(new Map());
|
const [validationCache, setValidationCache] = useState<Map<string, ValidationState>>(new Map());
|
||||||
// 环境变量展开结果缓存(key=path, value=expanded)
|
// 环境变量展开结果缓存(key=path, value=expanded)
|
||||||
const [expandedCache, setExpandedCache] = useState<Map<string, string>>(new Map());
|
const [expandedCache, setExpandedCache] = useState<Map<string, string>>(new Map());
|
||||||
|
|
||||||
|
const validatedRef = useRef<Set<string>>(new Set());
|
||||||
|
const expandedRef = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
// 过滤搜索
|
// 过滤搜索
|
||||||
const filtered = useMemo<PathRow[]>(() => {
|
const filtered = useMemo<PathRow[]>(() => {
|
||||||
if (!searchQuery) return paths.map((p, i) => ({ path: p, index: i }));
|
if (!searchQuery) return paths.map((p, i) => ({ path: p, index: i }));
|
||||||
@@ -43,13 +48,9 @@ export function PathTable({ tabId }: PathTableProps) {
|
|||||||
// 异步验证未缓存的路径
|
// 异步验证未缓存的路径
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const allPaths = paths;
|
const toValidate = paths.filter((p) => !validatedRef.current.has(p));
|
||||||
|
|
||||||
// 找出未缓存的路径
|
|
||||||
const toValidate = allPaths.filter((p) => !validationCache.has(p));
|
|
||||||
if (toValidate.length === 0) return;
|
if (toValidate.length === 0) return;
|
||||||
|
|
||||||
// 批量验证(限制并发 20)
|
|
||||||
const batch = toValidate.slice(0, 20);
|
const batch = toValidate.slice(0, 20);
|
||||||
Promise.all(
|
Promise.all(
|
||||||
batch.map(async (p): Promise<[string, ValidationState]> => {
|
batch.map(async (p): Promise<[string, ValidationState]> => {
|
||||||
@@ -63,30 +64,28 @@ export function PathTable({ tabId }: PathTableProps) {
|
|||||||
}),
|
}),
|
||||||
).then((results) => {
|
).then((results) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
for (const [p] of results) validatedRef.current.add(p);
|
||||||
setValidationCache((prev) => {
|
setValidationCache((prev) => {
|
||||||
const next = new Map(prev);
|
const next = new Map(prev);
|
||||||
for (const [p, v] of results) {
|
for (const [p, v] of results) next.set(p, v);
|
||||||
next.set(p, v);
|
|
||||||
}
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => { cancelled = true; };
|
||||||
cancelled = true;
|
}, [paths]);
|
||||||
};
|
|
||||||
}, [paths, validationCache]);
|
|
||||||
|
|
||||||
// 异步展开环境变量(用于 tooltip)
|
// 异步展开环境变量(用于 tooltip)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const toExpand = paths.filter(
|
const toExpand = paths.filter(
|
||||||
(p) => p.includes('%') && !expandedCache.has(p),
|
(p) => p.includes('%') && !expandedRef.current.has(p),
|
||||||
);
|
);
|
||||||
if (toExpand.length === 0) return;
|
if (toExpand.length === 0) return;
|
||||||
|
|
||||||
|
const batch = toExpand.slice(0, 20);
|
||||||
Promise.all(
|
Promise.all(
|
||||||
toExpand.map(async (p): Promise<[string, string]> => {
|
batch.map(async (p): Promise<[string, string]> => {
|
||||||
try {
|
try {
|
||||||
const expanded: string = await invoke('expand_env_vars', { path: p });
|
const expanded: string = await invoke('expand_env_vars', { path: p });
|
||||||
return [p, expanded !== p ? expanded : ''];
|
return [p, expanded !== p ? expanded : ''];
|
||||||
@@ -96,19 +95,16 @@ export function PathTable({ tabId }: PathTableProps) {
|
|||||||
}),
|
}),
|
||||||
).then((results) => {
|
).then((results) => {
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
|
for (const [p] of results) expandedRef.current.add(p);
|
||||||
setExpandedCache((prev) => {
|
setExpandedCache((prev) => {
|
||||||
const next = new Map(prev);
|
const next = new Map(prev);
|
||||||
for (const [p, v] of results) {
|
for (const [p, v] of results) next.set(p, v);
|
||||||
next.set(p, v);
|
|
||||||
}
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => { cancelled = true; };
|
||||||
cancelled = true;
|
}, [paths]);
|
||||||
};
|
|
||||||
}, [paths, expandedCache]);
|
|
||||||
|
|
||||||
// 所有路径默认有效(异步验证结果回来后再精确染色)
|
// 所有路径默认有效(异步验证结果回来后再精确染色)
|
||||||
const validations = useMemo(() => {
|
const validations = useMemo(() => {
|
||||||
@@ -118,7 +114,7 @@ export function PathTable({ tabId }: PathTableProps) {
|
|||||||
const isDuplicate = seen.has(lower);
|
const isDuplicate = seen.has(lower);
|
||||||
seen.add(lower);
|
seen.add(lower);
|
||||||
return {
|
return {
|
||||||
state: validationCache.get(path) ?? ('valid' as ValidationState),
|
state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
|
||||||
isDuplicate,
|
isDuplicate,
|
||||||
isEnvVar: path.includes('%'),
|
isEnvVar: path.includes('%'),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user