mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 01:45:54 +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 { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
@@ -11,6 +11,9 @@ interface PathRow {
|
||||
index: number;
|
||||
}
|
||||
|
||||
type ValidationState = 'valid' | 'invalid' | 'unknown';
|
||||
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';
|
||||
|
||||
export function PathTable({ tabId }: PathTableProps) {
|
||||
const sysPaths = useAppStore((s) => s.sysPaths);
|
||||
const userPaths = useAppStore((s) => s.userPaths);
|
||||
@@ -22,12 +25,14 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
const paths = tabId === 'system' ? sysPaths : userPaths;
|
||||
const isActive = activeTab === tabId;
|
||||
|
||||
type ValidationState = 'valid' | 'invalid' | 'unknown';
|
||||
// 本次会话中已验证过的路径缓存(key=path, value=ValidationState)
|
||||
const [validationCache, setValidationCache] = useState<Map<string, ValidationState>>(new Map());
|
||||
// 环境变量展开结果缓存(key=path, value=expanded)
|
||||
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[]>(() => {
|
||||
if (!searchQuery) return paths.map((p, i) => ({ path: p, index: i }));
|
||||
@@ -43,13 +48,9 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
// 异步验证未缓存的路径
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const allPaths = paths;
|
||||
|
||||
// 找出未缓存的路径
|
||||
const toValidate = allPaths.filter((p) => !validationCache.has(p));
|
||||
const toValidate = paths.filter((p) => !validatedRef.current.has(p));
|
||||
if (toValidate.length === 0) return;
|
||||
|
||||
// 批量验证(限制并发 20)
|
||||
const batch = toValidate.slice(0, 20);
|
||||
Promise.all(
|
||||
batch.map(async (p): Promise<[string, ValidationState]> => {
|
||||
@@ -63,30 +64,28 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
}),
|
||||
).then((results) => {
|
||||
if (cancelled) return;
|
||||
for (const [p] of results) validatedRef.current.add(p);
|
||||
setValidationCache((prev) => {
|
||||
const next = new Map(prev);
|
||||
for (const [p, v] of results) {
|
||||
next.set(p, v);
|
||||
}
|
||||
for (const [p, v] of results) next.set(p, v);
|
||||
return next;
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [paths, validationCache]);
|
||||
return () => { cancelled = true; };
|
||||
}, [paths]);
|
||||
|
||||
// 异步展开环境变量(用于 tooltip)
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const toExpand = paths.filter(
|
||||
(p) => p.includes('%') && !expandedCache.has(p),
|
||||
(p) => p.includes('%') && !expandedRef.current.has(p),
|
||||
);
|
||||
if (toExpand.length === 0) return;
|
||||
|
||||
const batch = toExpand.slice(0, 20);
|
||||
Promise.all(
|
||||
toExpand.map(async (p): Promise<[string, string]> => {
|
||||
batch.map(async (p): Promise<[string, string]> => {
|
||||
try {
|
||||
const expanded: string = await invoke('expand_env_vars', { path: p });
|
||||
return [p, expanded !== p ? expanded : ''];
|
||||
@@ -96,19 +95,16 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
}),
|
||||
).then((results) => {
|
||||
if (cancelled) return;
|
||||
for (const [p] of results) expandedRef.current.add(p);
|
||||
setExpandedCache((prev) => {
|
||||
const next = new Map(prev);
|
||||
for (const [p, v] of results) {
|
||||
next.set(p, v);
|
||||
}
|
||||
for (const [p, v] of results) next.set(p, v);
|
||||
return next;
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [paths, expandedCache]);
|
||||
return () => { cancelled = true; };
|
||||
}, [paths]);
|
||||
|
||||
// 所有路径默认有效(异步验证结果回来后再精确染色)
|
||||
const validations = useMemo(() => {
|
||||
@@ -118,7 +114,7 @@ export function PathTable({ tabId }: PathTableProps) {
|
||||
const isDuplicate = seen.has(lower);
|
||||
seen.add(lower);
|
||||
return {
|
||||
state: validationCache.get(path) ?? ('valid' as ValidationState),
|
||||
state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
|
||||
isDuplicate,
|
||||
isEnvVar: path.includes('%'),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user