From 68f4617bdaed03302f8c99f4a51ab68e2cb2ed96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Tue, 26 May 2026 22:43:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20PathTable=20=E2=80=94=20=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F=E5=B1=95=E5=BC=80=E9=99=90=E6=B5=81?= =?UTF-8?q?20=E5=B9=B6=E5=8F=91=E3=80=81=E6=B6=88=E9=99=A4useEffect?= =?UTF-8?q?=E5=8F=8C=E9=87=8D=E8=A7=A6=E5=8F=91=E3=80=81=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E6=94=B9=E4=B8=BA=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - expand useEffect 增加 .slice(0, 20) 批次限制,避免大量路径时并发过高 - validatedRef / expandedRef 替代 validationCache.has / expandedCache.has 过滤, 从 useEffect 依赖数组中移除缓存 state,消除双重触发 - ValidationState 类型提升到模块层级,新增 DEFAULT_VALIDATION_STATE 常量 替代硬编码类型断言 Co-Authored-By: Claude Opus 4.7 --- src/components/path-list/PathTable.tsx | 44 ++++++++++++-------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/components/path-list/PathTable.tsx b/src/components/path-list/PathTable.tsx index fc45b66..5c96184 100644 --- a/src/components/path-list/PathTable.tsx +++ b/src/components/path-list/PathTable.tsx @@ -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>(new Map()); // 环境变量展开结果缓存(key=path, value=expanded) const [expandedCache, setExpandedCache] = useState>(new Map()); + const validatedRef = useRef>(new Set()); + const expandedRef = useRef>(new Set()); + // 过滤搜索 const filtered = useMemo(() => { 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('%'), };