mirror of
https://github.com/LHY0125/PathEditor.git
synced 2026-06-29 09:55:56 +08:00
63c8ed424b
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
295 lines
8.0 KiB
Markdown
295 lines
8.0 KiB
Markdown
# 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` 常量,消除类型断言**
|
||
|
||
```typescript
|
||
// 在组件外部定义(文件顶部 import 之后)
|
||
const DEFAULT_VALIDATION_STATE: ValidationState = 'valid';
|
||
```
|
||
|
||
第 121 行改为:
|
||
```typescript
|
||
state: validationCache.get(path) ?? DEFAULT_VALIDATION_STATE,
|
||
```
|
||
|
||
- [ ] **Step 2: 环境变量展开加 20 并发上限**
|
||
|
||
第 88-96 行,在 `toExpand` 之后加批次限制:
|
||
```typescript
|
||
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`:
|
||
|
||
```typescript
|
||
// 新增两个 ref(放在 useState 声明之后)
|
||
const validatedRef = useRef<Set<string>>(new Set());
|
||
const expandedRef = useRef<Set<string>>(new Set());
|
||
```
|
||
|
||
验证 effect 改为:
|
||
```typescript
|
||
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 同理:
|
||
```typescript
|
||
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**
|
||
|
||
```typescript
|
||
// 第 1 行
|
||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||
```
|
||
|
||
- [ ] **Step 5: 编译 + 测试**
|
||
|
||
```bash
|
||
npx tsc --noEmit && npx vitest run
|
||
```
|
||
|
||
- [ ] **Step 6: Commit**
|
||
|
||
```bash
|
||
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)**
|
||
|
||
```rust
|
||
// 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 到文件顶部,消除重复字符串**
|
||
|
||
```rust
|
||
// 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 行),更新路径引用:
|
||
|
||
```rust
|
||
// 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**
|
||
|
||
```bash
|
||
cd src-tauri && cargo check && cargo clippy -- -D warnings
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
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 行之后)加:
|
||
|
||
```typescript
|
||
/** Tauri 的 File 对象扩展了标准 File,额外提供文件系统路径 */
|
||
interface TauriFile extends File {
|
||
path: string;
|
||
}
|
||
```
|
||
|
||
第 95 行改为:
|
||
```typescript
|
||
const file = e.dataTransfer.files[i] as TauriFile;
|
||
if (file.path) useAppStore.getState().addPath(file.path, activeTab === 'user' ? TargetType.USER : TargetType.SYSTEM);
|
||
```
|
||
|
||
- [ ] **Step 2: 编译检查**
|
||
|
||
```bash
|
||
npx tsc --noEmit
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
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 行添加注释**
|
||
|
||
```typescript
|
||
// 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: 编译检查**
|
||
|
||
```bash
|
||
npx tsc --noEmit
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add src/store/app-store.ts
|
||
git commit -m "docs: undo/redo 添加注释说明为何内联 isModified 而非调用 markDirty()"
|
||
```
|
||
|
||
---
|
||
|
||
## 执行顺序
|
||
|
||
Task 1 → 2 → 3 → 4,互不依赖但建议按序执行。
|
||
|
||
全部完成后运行完整验证:
|
||
```bash
|
||
npx tsc --noEmit && npx vitest run && cd src-tauri && cargo check && cargo clippy -- -D warnings
|
||
```
|