第01章:AI生成的useEffect依赖数组写错了
第01章:AI生成的useEffect依赖数组写错了
“AI 帮你写了一个 useEffect,里面用了5个变量,但 dependencies 数组只写了2个。ESLint 报警告,你不知道是不是可以忽略,就加了 // eslint-disable-next-line 注释。几天后出了一个 Bug——页面数据不刷新,或者相反,陷入无限请求循环。这两个问题都来自同一个根因:useEffect 依赖数组写错了。”
ℹ️ 版本说明:本章基于 React 19.2.6。
1.1 AI默认会生成什么
// AI 通常给你的代码(依赖数组不完整)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchUser(userId).then(setUser);
fetchPosts(userId, sortBy).then(setPosts); // sortBy 没有在依赖里!
}, [userId]); // ← 缺少 sortBy
}
// 或者 AI 帮你"解决"警告的方式:禁用 ESLint
useEffect(() => {
doSomething(a, b, c);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // ← 永远不更新
1.2 AI通常遗漏的4个坑
⚠️ 坑1:依赖遗漏 → 数据陈旧(Stale Closure)
// 问题:sortBy 是外部变量,变了也不触发 effect
function PostList({ userId }) {
const [sortBy, setSortBy] = useState('date');
const [posts, setPosts] = useState([]);
useEffect(() => {
// 这里的 sortBy 是 effect 创建时的值
// 即使 sortBy 变了,这个 effect 不会重新执行
fetchPosts(userId, sortBy).then(setPosts);
}, [userId]); // ← 缺少 sortBy!
return (
<>
<button onClick={() => setSortBy('likes')}>按点赞排序</button>
{/* 点击后 sortBy 变了,但 posts 不会更新! */}
</>
);
}
// 修复:依赖数组要完整
useEffect(() => {
fetchPosts(userId, sortBy).then(setPosts);
}, [userId, sortBy]); // ← 完整依赖
⚠️ 坑2:依赖是对象或函数 → 无限循环
// 问题:每次渲染都创建新的对象/函数
function UserList() {
const filters = { active: true, role: 'admin' }; // 每次渲染新对象!
useEffect(() => {
fetchUsers(filters).then(setUsers);
}, [filters]); // filters 每次渲染都变 → 无限循环!
// 修复方案1:把对象移到 useEffect 内部
useEffect(() => {
const filters = { active: true, role: 'admin' }; // 在 effect 内部定义
fetchUsers(filters).then(setUsers);
}, []); // 不需要依赖
// 修复方案2:用 useMemo 稳定引用(如果 filters 来自 state/props)
const filters = useMemo(() => ({ active, role }), [active, role]);
useEffect(() => {
fetchUsers(filters).then(setUsers);
}, [filters]); // filters 只在 active/role 变时才变
}
// 函数依赖同理:
// 每次渲染都创建新函数 → 无限循环
const fetchData = () => fetch('/api/data'); // 新函数!
useEffect(() => fetchData(), [fetchData]); // 无限循环
// 修复:useCallback 或把函数移进 effect
⚠️ 坑3:不该用 useEffect 的场景
// ❌ 这些场景不需要 useEffect:
// 场景1:根据 props 计算派生数据
function Component({ items }) {
const [sorted, setSorted] = useState([]);
useEffect(() => { // ❌ 不需要 effect
setSorted([...items].sort());
}, [items]);
// ✅ 直接在渲染时计算
const sorted = useMemo(() => [...items].sort(), [items]);
}
// 场景2:响应用户事件
function Form() {
const [value, setValue] = useState('');
const [result, setResult] = useState(null);
useEffect(() => { // ❌ 不需要 effect
if (value) validateInput(value).then(setResult);
}, [value]);
// ✅ 在事件处理里处理
const handleChange = async (e) => {
setValue(e.target.value);
if (e.target.value) {
const result = await validateInput(e.target.value);
setResult(result);
}
};
}
// 场景3:同步通知父组件
function Child({ onDataChange }) {
const [data, setData] = useState(null);
useEffect(() => { // ❌ 不需要 effect,会额外渲染一次
onDataChange(data);
}, [data, onDataChange]);
// ✅ 在修改 data 的同时通知父组件
const updateData = (newData) => {
setData(newData);
onDataChange(newData); // 直接调用
};
}
⚠️ 坑4:React 19 新方案:use() 替代 useEffect 数据获取
// React 19 之前:useEffect + useState 组合(容易出错)
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]); // 如果这里漏了 userId 就是 Bug
if (loading) return <Spinner />;
if (error) return <Error error={error} />;
return <div>{user?.name}</div>;
}
// React 19 新方案:use() hook(更简洁,无法出现依赖错误)
import { use, Suspense } from 'react';
function UserProfile({ userId }) {
// use() 接受 Promise,自动与 Suspense 集成
const user = use(fetchUser(userId)); // ← 没有依赖数组,没有 loading/error 状态!
return <div>{user.name}</div>;
}
// 父组件用 Suspense 和 ErrorBoundary 包裹
function App() {
return (
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Spinner />}>
<UserProfile userId={123} />
</Suspense>
</ErrorBoundary>
);
}
1.3 更好的提示词
提示词 P01:修复现有 useEffect
使用时机:你有一个 useEffect,行为异常(数据不更新或无限请求)
帮我检查和修复这个 React 19.2.6 组件的 useEffect。
当前代码:
[粘贴完整组件代码]
问题现象:
[选择一个:
- 数据不更新(修改 X 后,显示的还是旧数据)
- 无限请求循环(Network 面板里请求不停)
- 第一次加载正常,切换参数后不更新
- ESLint 报 react-hooks/exhaustive-deps 警告]
需要:
1. 找出依赖数组的问题(遗漏/多余/每次创建新引用)
2. 判断这个 useEffect 是否真的需要(还是用 useMemo/事件处理更合适)
3. 如果数据获取场景,是否可以改用 React 19 的 use() hook
4. 修复后的代码
基于 React 19.2.6。
提示词 P02:React 19 use() hook 数据获取
使用时机:用 use() 替代 useEffect + useState 组合获取数据
帮我用 React 19.2.6 的 use() hook 重写数据获取逻辑。
当前代码(useEffect 模式):
[粘贴 useEffect + useState 的数据获取代码]
要求:
1. 用 use() hook 替代 useEffect + loading/error state 组合
2. 配合 Suspense 处理 loading 状态
3. 配合 ErrorBoundary 处理错误(React 19 的 function ErrorBoundary 语法)
4. 如果有多个并发数据请求,如何用 use() 并行处理?
注意:
- use() 只能在组件顶层或循环中调用(不能在条件语句里)
- 需要对 Promise 进行缓存(不然每次渲染都创建新 Promise)
- 如何用 React.cache() 或外部缓存稳定 Promise 引用?
给我完整的代码,包括 Suspense 和 ErrorBoundary 的嵌套结构。
基于 React 19.2.6。
提示词 P03:useEffect 架构审查
使用时机:Review 整个组件文件,找出所有 useEffect 滥用
帮我审查这个 React 19.2.6 文件里的所有 useEffect,识别可以简化的模式。
代码文件:
[粘贴整个文件]
审查标准:
1. 依赖数组完整性:
- 是否有遗漏的依赖?(会导致数据陈旧)
- 是否有每次渲染都变的依赖?(会导致无限循环)
2. 是否真的需要 useEffect?对每个 effect 判断:
- 如果是根据 state/props 计算数据 → 改用 useMemo
- 如果是事件响应 → 改用事件处理函数
- 如果是数据获取 → 考虑改用 use() hook 或 React Query
- 如果是第三方库初始化(非React库) → useEffect 合适
3. React 19 可以简化的模式:
- useEffect + setState 数据获取 → use() hook
- useEffect + context → use(MyContext) 直接读取
- useEffect 同步外部状态 → useSyncExternalStore
给我按优先级排列的改进建议,以及每个改进的完整代码。
基于 React 19.2.6。
1.4 验收清单
| 检查项 | 验证方法 | AI辅助 |
|---|---|---|
| ESLint 无 exhaustive-deps 警告 | npm run lint 无警告 |
用 P01 修复 |
无 eslint-disable 注释 |
grep 搜索 eslint-disable | 用 P01 分析 |
| 数据获取不用 useEffect+useState | 用 use() 或 React Query | 用 P02 改造 |
| 对象依赖用 useMemo 稳定 | 无对象字面量在依赖数组里 | 用 P01 修复 |
| 派生数据用 useMemo 不用 useEffect | 无 useEffect → setState 计算模式 | 用 P03 审查 |
| 功能验证:修改参数后数据正确刷新 | 手动测试切换 userId 等参数 | - |
1.5 本章小结
如果你只记一件事:遇到 ESLint 的 react-hooks/exhaustive-deps 警告,不要用 // eslint-disable-next-line 忽略。要么把遗漏的依赖加上,要么认真想想这个 useEffect 是否真的必要——很多时候,计算派生数据用 useMemo,响应事件用事件处理函数,都比 useEffect 更好。
useEffect 依赖的三个层次:
- 正确的依赖数组(完整 + 稳定引用):所有在 effect 里用到的变量都要在依赖里,对象/函数依赖要用 useMemo/useCallback 稳定引用
- 识别不需要 useEffect 的场景(计算派生数据用 useMemo,事件响应用处理函数):减少不必要的 effect,降低 Bug 概率
- React 19 新方案(use() hook + Suspense):数据获取场景用 use(),彻底消除依赖数组问题,代码更简洁
→ 第2章:AI帮我用了useState但没有处理异步状态