Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
8.0 KiB
v4.1 第二轮代码清理 — 实现计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.
Goal: 修复第二轮深度审查发现的 7 个代码质量问题
Architecture: 7 个独立小修,互不冲突。PathTable.tsx 有 3 项相关改动,放在一个 Task 里。
Tech Stack: TypeScript strict + React + Rust + Tauri IPC
Task 1: PathTable 三合一(并发限制 + 双重触发 + 类型断言)
Files:
-
Modify:
src/components/path-list/PathTable.tsx:25, 53, 78, 88-96, 111, 121 -
Step 1: 提取
DEFAULT_VALIDATION_STATE常量,消除类型断言
// 在组件外部定义(文件顶部 import 之后)
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';
第 121 行改为:
state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
- Step 2: 环境变量展开加 20 并发上限
第 88-96 行,在 toExpand 之后加批次限制:
const toExpand = paths.filter(
(p) => p.includes('%') && !expandedCache.has(p),
);
if (toExpand.length === 0) return;
const batch = toExpand.slice(0, 20); // ← 新增:限制并发 20
Promise.all(
batch.map(async (p): Promise<[string, string]> => {
- Step 3: 消除 useEffect 双重触发
问题:validationCache 和 expandedCache 在依赖数组中,setState 后触发 effect 再次执行(空跑一轮)。
用 ref 跟踪"是否已经触发过验证",effect 只依赖 paths:
// 新增两个 ref(放在 useState 声明之后)
const validatedRef = useRef<Set<string>>(new Set());
const expandedRef = useRef<Set<string>>(new Set());
验证 effect 改为:
useEffect(() => {
let cancelled = false;
const toValidate = paths.filter((p) => !validatedRef.current.has(p));
if (toValidate.length === 0) return;
const batch = toValidate.slice(0, 20);
Promise.all(
batch.map(async (p): Promise<[string, ValidationState]> => {
try {
if (p.includes('%')) return [p, 'valid'];
const valid: boolean = await invoke('validate_path', { path: p });
return [p, valid ? 'valid' : 'invalid'];
} catch {
return [p, 'unknown'];
}
}),
).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);
return next;
});
});
return () => { cancelled = true; };
}, [paths]); // ← 移除 validationCache 依赖
展开 effect 同理:
useEffect(() => {
let cancelled = false;
const toExpand = paths.filter((p) => p.includes('%') && !expandedRef.current.has(p));
if (toExpand.length === 0) return;
const batch = toExpand.slice(0, 20);
Promise.all(
batch.map(async (p): Promise<[string, string]> => {
try {
const expanded: string = await invoke('expand_env_vars', { path: p });
return [p, expanded !== p ? expanded : ''];
} catch {
return [p, ''];
}
}),
).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);
return next;
});
});
return () => { cancelled = true; };
}, [paths]); // ← 移除 expandedCache 依赖
validations 和渲染逻辑不变。
- Step 4: 添加
useRef到 import
// 第 1 行
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
- Step 5: 编译 + 测试
npx tsc --noEmit && npx vitest run
- Step 6: Commit
git add src/components/path-list/PathTable.tsx
git commit -m "fix: PathTable — 环境变量展开限流20并发、消除useEffect双重触发、类型断言改为常量"
Task 2: backup.rs — use 语句移到文件顶部 + 消除路径字符串重复
Files:
-
Modify:
src-tauri/src/commands/registry.rs:4-5 -
Modify:
src-tauri/src/commands/backup.rs:1-3, 22-23, 34-42 -
Step 1: registry.rs 常量改为 pub(crate)
// src-tauri/src/commands/registry.rs — 第 4-5 行,加 pub(crate)
pub(crate) const SYS_REG_PATH: &str = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
pub(crate) const USER_REG_PATH: &str = "Environment";
pub(crate) const PATH_VALUE: &str = "Path";
- Step 2: backup.rs — 移动 use 到文件顶部,消除重复字符串
// src-tauri/src/commands/backup.rs — 文件顶部(第 1 行之后)
use chrono::Local;
use std::path::PathBuf;
use winreg::enums::*; // ← 从函数体内移出
// 第 4 行之后新增:
use crate::commands::registry::{self, SYS_REG_PATH, USER_REG_PATH};
删除函数体内的 use(第 22-23 行),更新路径引用:
// backup.rs — 第 34-42 行,用常量替换字符串字面量
let sys_paths = registry::load_paths(
HKEY_LOCAL_MACHINE,
SYS_REG_PATH, // ← 用常量
"系统",
)?;
let user_paths = registry::load_paths(
HKEY_CURRENT_USER,
USER_REG_PATH, // ← 用常量
"用户",
)?;
- Step 3: 编译 + clippy
cd src-tauri && cargo check && cargo clippy -- -D warnings
- Step 4: Commit
git add src-tauri/src/commands/registry.rs src-tauri/src/commands/backup.rs
git commit -m "refactor: backup.rs — use 语句移至文件顶部,注册表路径复用常量消除重复"
Task 3: AppShell — as any 拖拽路径类型安全
Files:
-
Modify:
src/components/layout/AppShell.tsx:95 -
Step 1: 定义 TauriFile 接口并消除 as any
在 AppShell 组件定义之前(第 16 行之后)加:
/** Tauri 的 File 对象扩展了标准 File,额外提供文件系统路径 */
interface TauriFile extends File {
path: string;
}
第 95 行改为:
const file = e.dataTransfer.files[i] as TauriFile;
if (file.path) useAppStore.getState().addPath(file.path, activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM);
- Step 2: 编译检查
npx tsc --noEmit
- Step 3: Commit
git add src/components/layout/AppShell.tsx
git commit -m "fix: AppShell 拖拽路径消除 as any,用 TauriFile 接口类型安全访问 path"
Task 4: app-store — undo/redo 加注释说明为何不用 markDirty()
Files:
-
Modify:
src/store/app-store.ts:210-226 -
Step 1: 在 undo/redo 的 isModified 行添加注释
// app-store.ts — 第 213 行之前加注释
undo: () => {
const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get();
const result = undoRedo.undo(sysPaths, userPaths);
if (result) {
set({
sysPaths: result[0], userPaths: result[1], selectedIndices: [],
// 内联 isModified 计算而非调用 markDirty(),避免两次 set() 渲染
isModified: !(arraysEqual(result[0], _savedSys) && arraysEqual(result[1], _savedUser)),
});
}
},
redo: () => {
const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get();
const result = undoRedo.redo(sysPaths, userPaths);
if (result) {
set({
sysPaths: result[0], userPaths: result[1], selectedIndices: [],
// 内联 isModified 计算而非调用 markDirty(),避免两次 set() 渲染
isModified: !(arraysEqual(result[0], _savedSys) && arraysEqual(result[1], _savedUser)),
});
}
},
- Step 2: 编译检查
npx tsc --noEmit
- Step 3: Commit
git add src/store/app-store.ts
git commit -m "docs: undo/redo 添加注释说明为何内联 isModified 而非调用 markDirty()"
执行顺序
Task 1 → 2 → 3 → 4,互不依赖但建议按序执行。
全部完成后运行完整验证:
npx tsc --noEmit && npx vitest run && cd src-tauri && cargo check && cargo clippy -- -D warnings