P PasteCode
失败模式

如何修复AI过度使用useEffect的问题

AI代理经常使用useEffect来处理派生状态、事件转换和数据获取,而这些本应使用useMemo、事件处理器或Server Components。

CursorClaude CodeCodexWindsurf Next.jsTypeScript
.md .json 更新于 2026年6月8日

代理将本应作为派生状态、事件处理器或Server Component获取的逻辑包装在useEffect中,从而导致额外渲染、闭包过时和竞态条件。

症状

useEffect被用于计算派生值或同步状态,而这些本可以简单计算得出。

"use client";
import { useState, useEffect } from "react";
// WRONG — useEffect to compute derived state
export function CartSummary({ items }: { items: CartItem[] }) {
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(items.reduce((sum, item) => sum + item.price * item.qty, 0));
}, [items]);
return <p>Total: ${total}</p>;
// Renders twice: once with total=0, once after the effect runs
}

另一种模式:在useEffect中获取数据,而Server Component可以无需任何客户端包开销完成。

"use client";
useEffect(() => {
fetch("/api/products").then((r) => r.json()).then(setProducts);
}, []);
// Waterfall: page loads -> JS executes -> fetch starts -> render

原因

useEffect是React 16/17中主要的副作用出口。基于React 18之前和App Router之前代码训练的模型默认学会了使用它。“我需要渲染后发生某些事情”的心智模型映射到useEffect,即使真正的修复更简单。

如何识别

  • useEffect设置了可以直接从props或其他状态计算得出的状态。
  • 在不在Server Component子树中的组件顶部使用useEffect(() => { fetch(...).then(setState) }, [])
  • useEffect没有清理函数,且依赖项是一个函数(闭包过时风险)。
  • useEffect仅用于挂载时的日志或分析。

如何修复

使用正确的工具:

// CORRECT — derived state: just compute it
export function CartSummary({ items }: { items: CartItem[] }) {
const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
return <p>Total: ${total.toFixed(2)}</p>;
}
// CORRECT — expensive derivation: useMemo
import { useMemo } from "react";
export function FilteredList({ items, query }: { items: Item[]; query: string }) {
const filtered = useMemo(
() => items.filter((i) => i.name.toLowerCase().includes(query.toLowerCase())),
[items, query]
);
return <ul>{filtered.map((i) => <li key={i.id}>{i.name}</li>)}</ul>;
}
app/products/page.tsx
// CORRECT — data fetching: async Server Component (no useEffect needed)
export default async function ProductsPage() {
const products = await fetch("https://api.example.com/products").then((r) =>
r.json()
);
return <ProductList products={products} />;
}
[ ] Derived values from props/state are plain calculations, not useEffect + setState
[ ] Expensive derivations use useMemo, not useEffect
[ ] Data fetching on mount moves to async Server Components or React Query/SWR
[ ] Event-driven side-effects (form submit, button click) are in event handlers
[ ] useEffect is reserved for: external system sync, subscriptions, cleanup
[ ] Every useEffect that sets state has a loading/error state and cleanup

修复提示

Fix Prompt
This component uses useEffect to compute derived state or fetch initial data.
Refactor it: replace derived-state effects with direct calculations or useMemo,
move initial data fetching to an async Server Component parent or to a
data-fetching library (SWR/React Query) with proper loading/error handling, and
keep useEffect only for genuine external-system synchronization that requires
cleanup. Explain each useEffect you keep and why it cannot be replaced.

测试

Terminal window
# Count useEffect calls — a high number is a smell worth reviewing
grep -rn "useEffect" --include="*.tsx" --include="*.ts" src/ app/ \
| grep -v "node_modules" \
| wc -l
# Flag useEffect+setState patterns for manual review
grep -A5 "useEffect" app/**/*.tsx 2>/dev/null | grep "setState\|set[A-Z]" \
&& echo "REVIEW: possible derived-state antipattern" || echo "OK"