SPEC-005 — React 前端 lifecycle 系统性扫描 + 修复¶
状态:parked(暂缓) — 见 frontmatter
parked_reason。
1. 背景¶
来源:[[2026-05-26-frontend-lifecycle-audit-blindspot|2026-05-26-前端lifecycle审查盲区]] — v0.10.45 第二轮独立 agent 元-评估发现。
问题:本会话累计修了 14+ 真 bug(ISSUE-0023~0029),但 0 个针对:
- useEffect cleanup 缺失
- setState on unmounted component
- AbortController for in-flight fetch
- React StrictMode 双 mount race
量化:grep -rn "useEffect\|useState" src/sections/ src/components/ → 334 处
- 用 ahooks/useRequest ✅ 有 unmount cancel 兜底
- 裸 useEffect(async () => { ... setState(...) }) —— 全部裸奔 ❌
- 仅 2 处用了 isMounted-style ref(且都是 drag 相关)
为什么一直没查:审查模式偏 background / MV3 / 持久化,React 前端是另一套心智模型,review 路径没经过这里。
2. 目标 / 非目标¶
目标¶
- Phase 1(扫描):写
scripts/scan-react-lifecycle.py类似scan:mv3,给出基线 + diff 模式 - Phase 2(修复):高频组件优先 — main-layout / data-view / local-data-view / task-view / settings-view / popup
- Phase 3(规则化):写
docs/rules/react-lifecycle-checklist.md+ pre-commit hook 在 src/sections/ 改动时跑扫描
非目标¶
- 不重写组件架构 — 仅补 cleanup / AbortController / isMounted guard
- 不强求 100% 修干净 — 优先高频路径
3. 实施估算¶
| Phase | 工作量 | 风险 |
|---|---|---|
| Phase 1 scan 脚本 | ~2h | 低 |
| Phase 2 高频组件修复(~10 个组件) | ~10h | 中(修错引入 regression) |
| Phase 3 rule + pre-commit | ~3h | 低 |
| 合计 | ~15h | — |
4. 检测模式(Phase 1)¶
# scripts/scan-react-lifecycle.py
PATTERNS = [
# 模式 1:裸异步 useEffect 无 cleanup
r"useEffect\(\s*async\s*\(\)\s*=>\s*\{",
# 模式 2:useEffect 内 setState 无 isMounted guard
r"useEffect\([\s\S]{0,500}set[A-Z]\w+\(",
# 模式 3:fetch / await 无 AbortController
r"useEffect\([\s\S]{0,500}(fetch|await)\s",
]
输出:每个文件每条命中给出文件:行号 + 上下文,按风险分级(裸异步最高,setState 内 useEffect 中等)。
5. 修复策略(Phase 2)¶
5.1 异步 useEffect 标准模板¶
useEffect(() => {
let cancelled = false;
const ac = new AbortController();
(async () => {
try {
const data = await fetch(url, { signal: ac.signal });
if (!cancelled) setData(data);
} catch (e) {
if (!cancelled && e.name !== 'AbortError') setError(e);
}
})();
return () => { cancelled = true; ac.abort(); };
}, [url]);
5.2 onMessage / storage listener 标准模板¶
useEffect(() => {
const handler = (msg) => { /* ... */ };
browser.runtime.onMessage.addListener(handler);
return () => browser.runtime.onMessage.removeListener(handler);
}, []);
ISSUE-0029 S4 (local-data-view onMessage 无 cleanup) 是这类问题的第一个被修,可作为参考。
5.3 优先级矩阵¶
| 组件 | 风险 | 估时 |
|---|---|---|
| local-data-view | 已修一处,仍可能有遗漏 | 1h |
| main-layout | mount 早、用户全程在 | 1.5h |
| task-view | 高频切换 | 1h |
| settings-view | 表单 state 多 | 1h |
| data-view | 大列表 + polling | 1.5h |
| popup | 反复 mount/unmount | 1h |
| 其他 sections | — | 2h |
6. 触发条件(出 parked 的时机)¶
任意一条达成即可:
- 用户反馈"扩展用久了切 tab 卡 / 数据延迟更新 / 偶发奇怪状态"
- 下次 React 相关大改动(重构某 view / 升级 React 版本)顺手做 Phase 1
- 后续 agent review 又发现同类问题集中冒头
7. 紧迫度评估(来自原始素材)¶
React 17/18 unmount race 大多数情况下表现为 console warning,不是用户可见 bug,但长期可能导致内存泄漏 / 偶发奇怪状态。真要触发严重 bug 需要:快速 mount/unmount + setState race + 错误数据回流。
MV3 SW 经常被 kill 也变相缓解了部分内存累积。
8. 元教训¶
审查盲区的发现需要元-评估视角。4 轮自我 review + 第一轮独立 agent 都聚焦"找具体 bug",没人问"我们是不是一直没查某类风险"。第二轮 agent 明确被 brief 这个角度才发现。
下次写 rule 时考虑加:"每 N 轮审查后跑一次元-评估,问:是否某类风险从未被检查过"。
相关¶
- [[2026-05-26-frontend-lifecycle-audit-blindspot|2026-05-26-前端lifecycle审查盲区]] — 原始发现
- [[0029-dataview-perf-listener-no-cleanup|0029-DataView性能浪费索引-listener无清理]] — 同类问题第一个被修的 issue
- 独立agent审查 — 元-评估方法论