跳转至

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 的时机)

任意一条达成即可:

  1. 用户反馈"扩展用久了切 tab 卡 / 数据延迟更新 / 偶发奇怪状态"
  2. 下次 React 相关大改动(重构某 view / 升级 React 版本)顺手做 Phase 1
  3. 后续 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审查 — 元-评估方法论