你是不是也这样?写React组件时,只要碰到“组件挂载后要做点事”“数据加载”“状态联动”“外部系统同步”,第一反应就是甩一个useEffect进去?

结果呢?项目越大,代码越像定时炸弹——无限循环突然出现、竞态条件莫名其妙、依赖数组一改就崩、调试时永远在问“这个effect到底为什么跑了/没跑”。

Factory团队(就是那个用AI大规模造软件的工厂)直接下狠手:团队内部规则——彻底禁用直接调用useEffect

听起来极端?但他们上线大项目后,bug数量大幅下降、 onboarding速度加快、AI写代码也不再埋雷。连React官方文档都专门写了《You Might Not Need an Effect》,证明这不是一家之言,而是现代React的正确打开方式。

今天就把他们的5个替代模式一次性拆透。照着改,你的React代码会瞬间从“容易炸”变成“一看就懂、改不动就崩”。
useEffect要禁用

1. 派生状态(derive state),别去同步它

90%的useEffect都是在“用状态同步另一个状态”。这不但多一次渲染,还埋下循环隐患。

坏例子(经典无限循环陷阱)

// ❌ 两个render周期 + 潜在循环
const [products, setProducts] = useState([]);
const [filtered, setFiltered] = useState([]);

useEffect(() => {
  setFiltered(products.filter(p => p.inStock));
}, [products]);

好例子(一行搞定)

// ✅ 一个render,零副作用
const filtered = products.filter(p => p.inStock);

金句:你以为useEffect是在“响应变化”,其实它只是把React已经能自动算的东西,硬生生变成了手动同步。

闻一闻就知道要重构的味道:你要写useEffect(() => setX(deriveFromY(y)), [y])的时候,停手。

2. 用数据查询库,别自己手写fetch + effect

手写useEffect去fetch是最常见的竞态条件制造机。

坏例子

// ❌ 页面切换时旧请求还在跑
useEffect(() => {
  fetchProduct(id).then(setProduct);
}, [id]);

好例子

// ✅ useQuery自动处理取消、缓存、loading、重试
const { data: product } = useQuery(['product', id], () => fetchProduct(id));

闻味道:你的effect里出现了fetchsetState,立刻换库。

3. 用事件处理函数,别用effect当“触发器”

最离谱的用法:点个按钮 → set一个flag → effect看到flag才执行真实操作。

坏例子

// ❌ setLiked → effect触发 → 又set回false
useEffect(() => {
  if (liked) {
    postLike();
    setLiked(false);
  }
}, [liked]);

好例子

// ✅ 直接在onClick里干活
<button onClick={() => postLike()}>点赞</button>

闻味道:你用状态当“开关”让effect干活,果断删掉。

4. 真正需要挂载时,用useMountEffect

团队唯一允许的例外:确实要和外部系统同步(DOM focus、第三方widget、浏览器API)。

他们封装了一个显式命名的hook:

export function useMountEffect(effect: () => void | (() => void)) {
  useEffect(effect, []);  // 内部还是useEffect,但外部看不出
}

这让意图一目了然,也方便eslint强制禁止裸useEffect。

正确用法

  • 视频播放器真正加载完才play
  • key={videoId}强制remount,而不是用effect监听id变化

5. 用key强制重置组件,别用effect choreograph

想让videoId变了就“重头开始”?别折腾依赖数组,直接用React原生的remount机制。

坏例子:effect监听id去reload
好例子

<VideoPlayer key={videoId} videoId={videoId} />

闻味道:你的effect唯一目的就是“id变了就重置状态”,改用key。

禁useEffect的真正威力:强制函数 + 更干净的组件树

这个规则最大的价值不是少写一个hook,而是强制函数

  • 父组件负责编排生命周期和前提条件
  • 子组件可以假设“该有的都已经有了”
  • 控制流变得显式、事件驱动,而不是隐式同步

这其实就是Unix哲学在React上的体现:每个组件只干一件事,协调发生在清晰边界。

你选哪种bug?

没有团队能零bug,问题是你想选哪种失败模式:

  • useMountEffect坏了:要么跑了,要么没跑,二元且大声
  • 裸useEffect坏了:慢慢腐烂、flaky、性能下降、循环,最后才爆炸

Factory团队跑了大项目后,结论是:前者明显更好调试,后者是慢性毒药

本质上,这是一个“声明式 vs 隐式同步”的问题

useEffect把React的声明式本质,偷偷变成了“值和副作用的隐式关系管理”。
禁掉它,就是把权力交回给事件处理函数、派生状态和查询库,让代码重新变得可预测、可追溯、可被AI安全编写。

一句话总结
useEffect不是你的救兵,它才是现代React项目里隐藏最深的bug工厂。
彻底禁掉它,你会发现:代码不是变少了,而是真正变好了。

你现在项目里useEffect最多用在哪?欢迎评论区分享你的“血泪史”,我们一起讨论怎么一步步替换。

我是紫微AI,我们下期见。
(完)

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐