🚨别再滥用 useEffect 了!90% React Bug 的根源就在这

你有没有发现一个现象:

  • 只要写 React,就离不开 useEffect
  • 数据变了 → 加 useEffect
  • 不知道逻辑放哪 → 塞 useEffect
  • 页面不更新 → 再加一层 useEffect

写到最后:

  • 组件里一半代码都是 useEffect
  • 无限循环、重复请求、莫名其妙重渲染、闭包陷阱满天飞
  • 改 Bug 比写功能还累

这篇文章只讲一件事:
useEffect 到底是什么?以及它为什么被 90% 的人用错?

先讲背景:useEffect 到底是干嘛的?

早期 React 组件,有一堆生命周期: componentDidMountcomponentDidUpdatecomponentWillUnmount... 逻辑散得到处都是,维护巨痛苦。

Hook 出来后,React 想解决一个问题:

把"跟渲染无关、跟外部交互"的逻辑,统一收拢。

于是有了 useEffect

它的定位非常清晰:处理副作用(Side Effect)

什么是副作用?就是跳出 React 渲染逻辑、去跟外部打交道的操作:

  • 请求 API 接口
  • 操作真实 DOM(比如聚焦第三方库)
  • 定时器、延时
  • 局事件监听(resize、keydown)
  • 本地存储、document.title
  • 同步外部系统(日志、埋点)

一句话总结:只有需要和"外部世界"同步时,才需要 useEffect。

致命误解:你把它当成了 "监听器"?

这是 React 新手最大的误区。

你以为它是:监听某个变量变化,然后执行逻辑。 但 React 的核心模型是:UI = f (state)(纯函数)

请死死记住这句话:useEffect 不是 "监听变量变化",而是 "处理副作用"。

一旦滥用,React 内部发生了什么?

你写了一个逻辑,React 执行了一条死循环:

render (渲染) → effect (执行副作用) → setState (更新状态) → render (再次渲染) → effect ...

你以为只写了几行代码,其实你在 React 里开了一条高速公路,车多了自然堵车。

滥用 useEffect 的三大灾难

  1. 多余渲染暴增(性能杀手):一次逻辑触发多次渲染,页面卡顿、掉帧。
  2. 依赖链混乱(Bug 温床):依赖数组稍微不严谨,就陷入无限循环,或者闭包陷阱数据对不上。
  3. 逻辑碎片化(维护灾难):一个功能拆碎在多个不同的 useEffect 里,逻辑碎片化,谁敢动?

典型灾难链:

A 改 B → B 改 C → C 再改 A

你以为你在写逻辑,其实你在堆 Bug

这 4 种场景,绝对别用 useEffect

1. 计算状态 → 直接算,别存状态

js 复制代码
// ❌ 错误:多此一举,引发重复渲染 
const [a, setA] = useState(1) 
const [b, setB] = useState(0) 

useEffect(() => { 
  setB(a * 2) 
}, [a])
js 复制代码
// ✅ 正确:直接计算
const a = 1 
const b = a * 2 // 直接计算

能通过现有状态直接算出来的,就不要单独存状态,避免多余的渲染和逻辑。

2. 交互逻辑 → 写在事件处理函数里,不是 useEffect

js 复制代码
// ❌ 错误:为了弹个提示,监听整个count
useEffect(() => {
  if (count === 10) alert('够了')
}, [count])
js 复制代码
// ✅ 正确:点击时直接判断
const handleClick = () => {
  const newCount = count + 1
  setCount(newCount)
  if (newCount === 10) alert('够了')
}

用户主动触发的行为,不属于副作用同步,理应写在对应的事件处理函数中。

3. 初始化数据 → useState 初始值就能搞定

js 复制代码
// ❌ 错误:多一次render
const [user, setUser] = useState(null)
useEffect(() => {
  setUser(currentUser)
}, [])
js 复制代码
// ✅ 正确:一步到位,直接初始化
const [user] = useState(currentUser)

4. Props 同步 → 直接用 props,不要本地状态+effect

js 复制代码
// ❌ 错误:典型反模式,数据来源不单一
useEffect(() => {
  setValue(props.value)
}, [props.value])
js 复制代码
// ✅ 直接用 props
const { value } = props

🎯 useEffect 的唯一合法使用场景

只记 5 种合法场景,多一个都不用:

  1. 接口请求(记得必须带 AbortController 清理)
  2. 定时器 / 延时(必须 clear)
  3. 手动操作 DOM
  4. 全局事件监听(addEventListener 必须 remove)
  5. 同步外部系统(localStorage、title、埋点)

除此之外,能不用就不用。

结尾

很多人以为问题在 useEffect,其实问题在这里:

你有没有把组件当成"纯函数"?

通俗来讲,React 组件本该是纯函数:固定的 Props 和 State,就输出固定的 UI,不掺杂多余的副作用。

滥用 useEffect 就是强行打破这个规则,在渲染中乱加状态修改、异步逻辑,才引发各种 Bug 和性能问题。

你认为呢?Vue 的 Watch 是不是也是这个道理?欢迎在评论区一起讨论 ~~

相关推荐
河马老师2 小时前
写这需求快崩溃了,幸好我会装饰器模式
前端·javascript·面试
未来转换2 小时前
Python-web开发之Flask框架入门
前端·python·flask
用户5757303346242 小时前
🚀 拒绝“CSS 命名困难症”!手把手带你用 Tailwind CSS 搓一个“高颜值”登录页
前端
文静小土豆2 小时前
标签和选择器(Label和 Selector)
linux·前端
wuhen_n2 小时前
《Vue3+TS+Vite 高效编程与优化实践》专栏收尾
前端·javascript·vue.js
神奇小汤圆2 小时前
5分钟 快速上手 Function Calling
面试
kevinten102 小时前
折腾三个月,我把摩旅路线和 AI 搞在一起了
前端
偷光2 小时前
大模型核心技术概述:Token、Prompt、Tool与Agent的关系详解
前端·ai·prompt·ai编程
鹏程十八少2 小时前
8. Android 深入插件化Shadow源码:揭秘插件Activity启动的完整链路(源码解析)
java·前端·面试