你有没有发现一个现象:
- 只要写 React,就离不开
useEffect - 数据变了 → 加 useEffect
- 不知道逻辑放哪 → 塞 useEffect
- 页面不更新 → 再加一层 useEffect
写到最后:
- 组件里一半代码都是 useEffect
- 无限循环、重复请求、莫名其妙重渲染、闭包陷阱满天飞
- 改 Bug 比写功能还累
这篇文章只讲一件事:
useEffect 到底是什么?以及它为什么被 90% 的人用错?
先讲背景:useEffect 到底是干嘛的?
早期 React 组件,有一堆生命周期:
componentDidMount、componentDidUpdate、componentWillUnmount…
逻辑散得到处都是,维护巨痛苦。
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 的三大灾难
- 多余渲染暴增(性能杀手):一次逻辑触发多次渲染,页面卡顿、掉帧。
- 依赖链混乱(Bug 温床):依赖数组稍微不严谨,就陷入无限循环,或者闭包陷阱数据对不上。
- 逻辑碎片化(维护灾难):一个功能拆碎在多个不同的 useEffect 里,逻辑碎片化,谁敢动?
典型灾难链:
A 改 B → B 改 C → C 再改 A
你以为你在写逻辑,其实你在堆 Bug。
这 4 种场景,绝对别用 useEffect
1. 计算状态 → 直接算,别存状态
// ❌ 错误:多此一举,引发重复渲染
const [a, setA] = useState(1)
const [b, setB] = useState(0)
useEffect(() => {
setB(a * 2)
}, [a])
// ✅ 正确:直接计算
const a = 1
const b = a * 2 // 直接计算
能通过现有状态直接算出来的,就不要单独存状态,避免多余的渲染和逻辑。
2. 交互逻辑 → 写在事件处理函数里,不是 useEffect
// ❌ 错误:为了弹个提示,监听整个count
useEffect(() => {
if (count === 10) alert('够了')
}, [count])
// ✅ 正确:点击时直接判断
const handleClick = () => {
const newCount = count + 1
setCount(newCount)
if (newCount === 10) alert('够了')
}
用户主动触发的行为,不属于副作用同步,理应写在对应的事件处理函数中。
3. 初始化数据 → useState 初始值就能搞定
// ❌ 错误:多一次render
const [user, setUser] = useState(null)
useEffect(() => {
setUser(currentUser)
}, [])
// ✅ 正确:一步到位,直接初始化
const [user] = useState(currentUser)
4. Props 同步 → 直接用 props,不要本地状态+effect
// ❌ 错误:典型反模式,数据来源不单一
useEffect(() => {
setValue(props.value)
}, [props.value])
// ✅ 直接用 props
const { value } = props
🎯 useEffect 的唯一合法使用场景
只记 5 种合法场景,多一个都不用:
- 接口请求(记得必须带 AbortController 清理)
- 定时器 / 延时(必须 clear)
- 手动操作 DOM
- 全局事件监听(addEventListener 必须 remove)
- 同步外部系统(localStorage、title、埋点)
除此之外,能不用就不用。
结尾
很多人以为问题在 useEffect,其实问题在这里:
你有没有把组件当成“纯函数”?
通俗来讲,React 组件本该是纯函数:固定的 Props 和 State,就输出固定的 UI,不掺杂多余的副作用。
滥用 useEffect 就是强行打破这个规则,在渲染中乱加状态修改、异步逻辑,才引发各种 Bug 和性能问题。
你认为呢?Vue 的 Watch 是不是也是这个道理?欢迎在评论区一起讨论 ~~