主题
04 · Effects 与数据同步:何时该用 / 不该用
目标:建立「多数时候不需要 Effect」的肌肉记忆;会写依赖数组,能识别派生状态误用。
官方主文:Synchronizing with Effects · You Might Not Need an Effect · Lifecycle of reactive effects
1. Effect 是干什么的
useEffect 让你在渲染之外同步外部系统:订阅、定时器、非 React widget、与浏览器 API 握手等。
它不是:
- Vue
watch的默认替身 - 「一进页面就请求数据」的唯一答案(在 Next 里,读数据优先 Server Component,见 07)
- 根据 props/state 算出另一个 state 的地方
官方专文标题已经写明:You Might Not Need an Effect。
2. 先会判断:派生 vs Effect
| 场景 | 做法 | 错误示范 |
|---|---|---|
fullName = first + last | 渲染时直接算 | useEffect 里 setFullName |
| 过滤列表 | items.filter(...) | effect 里同步到 filtered state |
| 重置表单当 userId 变 | 用 key={userId} 重置组件,或事件里重置 | 复杂 effect 链 |
订阅 window resize | useEffect + cleanup | 无 cleanup 泄漏 |
| 点击按钮改 state | 事件处理器里 setState | effect「监听」按钮 state 再 set |
Vue 对照:computed → 渲染期计算;watch/onMounted 才更接近 Effect——且 Next 服务端读数往往根本不进浏览器 Effect。
3. 手敲:一个需要 Effect 的例子 + 一个不需要的反例
3.1 需要:同步 document.title
新建 app/effect-lab/page.tsx:
tsx
'use client'
import { useEffect, useState } from 'react'
export default function EffectLabPage() {
const [title, setTitle] = useState('Effect Lab')
useEffect(() => {
const prev = document.title
document.title = title
return () => {
document.title = prev
}
}, [title])
return (
<main style={{ padding: 24 }}>
<h1>Effect Lab</h1>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
<p>改输入,看浏览器标签。</p>
</main>
)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
要点:依赖 [title];返回清理函数。对照 Lifecycle of reactive effects。
3.2 不需要:过滤列表(刻意写坏再改对)
坏写法(学习用,不要留在项目里):
tsx
// ❌ 派生状态 + effect
const [query, setQuery] = useState('')
const [items, setItems] = useState(['alpha', 'beta', 'gamma'])
const [filtered, setFiltered] = useState(items)
useEffect(() => {
setFiltered(items.filter((x) => x.includes(query)))
}, [items, query])1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
好写法:
tsx
const filtered = items.filter((x) => x.includes(query))1
在同一页下方实现搜索过滤,用好写法;自己对照官方「You Might Not Need」里的同类例子。
4. 依赖数组速查(防 AI 乱删依赖)
| 写法 | 含义 |
|---|---|
[] | 挂载后跑一次;清理在卸载(仍要注意严格模式开发态双调用) |
[a, b] | a/b 变就重跑 |
| 省略依赖 | 禁止;ESLint react-hooks/exhaustive-deps 会吵,听它的 |
AI 若说「为了消警告删掉 deps」——对照官方,优先改代码结构,而不是骗 linter。
5. 和 Next 的衔接(预习)
| 需求 | 优先路径 |
|---|---|
| 首屏列表数据 | Server Component 里 fetch(07) |
| 提交表单写库 | Server Actions(08) |
| 浏览器-only API | Client + Effect |
| 按输入过滤已有列表 | 客户端派生,无需 Effect |
6. 必做验收
- [ ] 读完「Synchronizing with Effects」与「You Might Not Need an Effect」
- [ ]
/effect-lab能改 title,并有 cleanup - [ ] 过滤列表示例用渲染期计算,而不是 effect +
setFiltered - [ ] 能向别人用 20 秒说清:Effect 同步外部系统,不算派生
7. AI 提示词约束(本篇)
text
对照 https://react.dev/learn/you-might-not-need-an-effect
审查下列代码:标出每个 useEffect 是否必要。
对不必要的,给出「删除 effect、改为事件或派生」的最小补丁。
不要引入外部库。1
2
3
4
2
3
4
