跳转至

Rule — React 前端 lifecycle 清单

类比 MV3持久化陷阱清单:那个管 background;这个管前端。

为什么需要

v0.10.45 第二轮独立 agent 元-评估发现:本会话 14+ issue 0 个针对前端 lifecycle。 334 处 useEffect/useState 中除了用 useRequest 的,其余裸奔(无 cleanup)。

具体踩坑: - v0.10.45 ISSUE-0029 S4:local-data-view onMessage 无 cleanup - v0.10.46 ISSUE-0030:use-countdown 双 useEffect 反模式

这两个都是同一类问题:useEffect 注册副作用但忘了 cleanup

自查清单

1️⃣ useEffect 注册的副作用必须 cleanup

副作用 必须 cleanup
setInterval / setTimeout(长延迟) clearInterval/Timeout
addEventListener removeEventListener
chrome.runtime.onMessage.addListener / webext-bridge onMessage ✅ 返回 off()
new EventSource / new WebSocket .close()
new IntersectionObserver / MutationObserver / ResizeObserver .disconnect()
setState(同步 / async) 🟢 不必(但要看 race)
useRequest(ahooks) 🟢 自带 unmount cancel

2️⃣ 标准 pattern

// ✅ 正确
useEffect(() => {
  const timer = setInterval(() => { ... }, 1000);
  const off = onMessage('xxx', handler);
  window.addEventListener('resize', onResize);

  return () => {
    clearInterval(timer);
    off?.();
    window.removeEventListener('resize', onResize);
  };
}, []);
// ❌ 反模式 1:忘 cleanup
useEffect(() => {
  setInterval(...);
}, []);

// ❌ 反模式 2:拆两个 useEffect 用 mod-let 传递(v0.10.46 修过这个)
let timer;
useEffect(() => { timer = setInterval(...); }, []);
useEffect(() => () => clearInterval(timer), [timer]);

3️⃣ async fetch + setState 的 unmount race

// ❌ 反模式:unmount 后 setState warning
useEffect(() => {
  fetch(url).then(d => setData(d));  // 组件 unmount 后还可能 setData
}, []);

// ✅ 用 useRequest(推荐)
const { data } = useRequest(() => fetch(url));

// ✅ 或手动 mounted ref
useEffect(() => {
  let alive = true;
  fetch(url).then(d => { if (alive) setData(d); });
  return () => { alive = false; };
}, []);

4️⃣ AbortController for 真的能取消的 fetch

useEffect(() => {
  const ctrl = new AbortController();
  fetch(url, { signal: ctrl.signal }).then(...);
  return () => ctrl.abort();
}, []);

自查命令

# v0.10.46 起一键
pnpm scan:react

# 保存基线
pnpm scan:react -- --save-baseline

# 只显示新增(pre-commit 用)
pnpm scan:react -- --diff

脚本:scripts/scan-react-lifecycle.py。扫所有 useEffect 块内含副作用 keyword (addListener / setInterval / new EventSource 等)但无 return cleanup 的位置。

判定指南(写在脚本输出里): - 🔴 addListener / onMessage / EventSource → 几乎必修 - 🟡 setInterval / setTimeout → 看是否别处清理 - 🟢 仅 setState → 不必

触发场景

场景 必跑 / 可选
写完 React 组件 + 含 useEffect 必跑
review 1-2 版本前的前端代码 必跑
debug "扩展用久了变卡 / 数据不更新" 必跑
跨平台 / 浏览器特定 bug 可选

工作流

写新组件 / 改老组件
含 useEffect 副作用?
  ↓ 是
确认 return cleanup
git commit
pre-commit hook 自动跑 scan:react --diff
0 新增 → 通过
有新增 → 阻止 commit 或要求 --save-baseline 重存

与 useRequest 的关系

ahooks 的 useRequest 已经处理了: - unmount 自动 cancel - 轮询的 cleanup - error / loading 状态管理

优先用 useRequest,自己写裸 useEffect+async 是反模式。

反模式速查

❌ "我用了 [] deps 所以不需要 cleanup" → 错。cleanup 是 unmount 时跑,与 deps 无关 ❌ "setInterval 1s 短的没事" → 错。组件 unmount 后 setInterval 还在跑 = 内存泄漏 + 后台逻辑乱 ❌ "我手动 mounted ref 就够了" → 部分对。但 addListener 仍需 cleanup(不止 setState 问题) ❌ "我用两个 useEffect,一个注册一个 cleanup" → 反模式(v0.10.46 ISSUE-0030 案例)

历史案例

  • [[0029-dataview-perf-listener-no-cleanup|0029-DataView性能浪费索引-listener无清理]] — onMessage 无 cleanup
  • 待加:v0.10.46 ISSUE-0030 use-countdown 双 useEffect 反模式

元规则

技术规则(如本文)能扫到的:useEffect cleanup 缺失。 技术规则扫不到的:业务逻辑层面 unmount race(如组件依赖 prop 变化)。 后者需要独立agent审查