团队 AI 写了 3 个月 React,代码差点烂掉 —— 5 个坑 + 自动化防线

上周五 code review,我一口气打回了 6 个 PR。全是 AI 生成的 React 代码,看着能跑,但细看全是雷。

不是说 AI 写代码不行 ------ 说实话,这 3 个月我们团队的开发速度确实快了 3 倍。问题在于:AI 生成代码的速度是人类能审的 10 倍。当 review 跟不上产出,技术债就开始指数级堆积。

最近掘金热榜也在讨论这事 ------ "React 正在被 AI 投毒"。我不完全同意"投毒"这个说法,但如果你也在用 Cursor、Copilot 或者各种 AI Agent 写 React,下面这 5 个坑你大概率踩过。

先说结论

典型症状 影响 自动化检测
无意义 re-render 每次 props 都是新对象 页面卡顿 ESLint 插件
状态管理混乱 啥都往 useState 里塞 维护噩梦 CR 规范
密钥硬编码 API Key 直接写字符串 安全事故 git-secrets
useEffect 滥用 一个组件 5 个 effect 竞态 bug strict mode
缺失错误边界 子组件一崩全崩 白屏 ESLint 规则

有研究数据显示,AI 生成的代码引入 XSS 漏洞的概率是人写的 2.74 倍 ,硬编码密钥的概率是 2.1 倍。这不是危言耸听。

坑一:无意义 re-render

AI 特别喜欢在 JSX 里直接写内联对象和箭头函数。看着没毛病,但每次渲染都创建新引用,子组件全部重新渲染。

tsx 复制代码
// ❌ AI 最爱写的代码
function UserList({ users }) {
  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          style={{ marginBottom: 16, padding: '12px 20px' }}
          onClick={() => handleClick(user.id)}
          config={{ showAvatar: true, theme: 'light' }}
        />
      ))}
    </div>
  );
}

三个坑点:style 内联对象、onClick 箭头函数、config 对象 ------ 每次渲染都是新引用。如果 UserCard 用了 React.memo,完全白费。

tsx 复制代码
// ✅ 修正版
const cardStyle = { marginBottom: 16, padding: '12px 20px' };
const cardConfig = { showAvatar: true, theme: 'light' };

function UserList({ users }) {
  const handleCardClick = useCallback((userId: string) => {
    handleClick(userId);
  }, []);

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          style={cardStyle}
          onClick={handleCardClick}
          userId={user.id}
          config={cardConfig}
        />
      ))}
    </div>
  );
}

实测数据:一个 500 条列表的页面,修完这一个问题,滚动帧率从 24fps 涨到 58fps。

坑二:状态管理灾难

让 AI 写一个表单页面,它会给你搞出七八个 useState

tsx 复制代码
// ❌ AI 的 useState 大法
function OrderForm() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [address, setAddress] = useState('');
  const [city, setCity] = useState('');
  const [zipCode, setZipCode] = useState('');
  const [country, setCountry] = useState('CN');
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [error, setError] = useState(null);
  const [touched, setTouched] = useState({});
  // ... 还有更多

  const handleSubmit = async () => {
    setIsSubmitting(true);
    setError(null);
    try {
      await api.createOrder({ name, email, phone, address, city, zipCode, country });
    } catch (e) {
      setError(e.message);
    } finally {
      setIsSubmitting(false);
    }
  };
}

10 个独立的 state,改一个字段触发一次渲染,提交按钮的状态和表单数据混在一起。后面要加验证逻辑?地狱模式。

tsx 复制代码
// ✅ useReducer + 关注点分离
interface OrderState {
  data: OrderFormData;
  status: 'idle' | 'submitting' | 'error';
  error: string | null;
}

const initialState: OrderState = {
  data: { name: '', email: '', phone: '', address: '', city: '', zipCode: '', country: 'CN' },
  status: 'idle',
  error: null,
};

function orderReducer(state: OrderState, action: OrderAction): OrderState {
  switch (action.type) {
    case 'UPDATE_FIELD':
      return { ...state, data: { ...state.data, [action.field]: action.value } };
    case 'SUBMIT_START':
      return { ...state, status: 'submitting', error: null };
    case 'SUBMIT_SUCCESS':
      return { ...state, status: 'idle' };
    case 'SUBMIT_ERROR':
      return { ...state, status: 'error', error: action.error };
    default:
      return state;
  }
}

function OrderForm() {
  const [state, dispatch] = useReducer(orderReducer, initialState);

  const handleFieldChange = useCallback((field: string, value: string) => {
    dispatch({ type: 'UPDATE_FIELD', field, value });
  }, []);
  // ...
}

一个 state 对象管所有字段,状态变更可追踪可调试。加验证?在 reducer 里统一处理。

坑三:密钥硬编码

这是最危险的。AI 写 API 调用示例时,经常直接把 key 写死:

tsx 复制代码
// ❌ 你以为 AI 不会写这种?它会的
const openai = new OpenAI({
  apiKey: 'sk-proj-abc123xyz...',
  dangerouslyAllowBrowser: true  // 双重作死
});

// 或者藏在配置对象里
const config = {
  firebase: {
    apiKey: "AIzaSyC1234567890abcdef",
    authDomain: "myapp.firebaseapp.com",
  }
};

团队里有个实习生直接把 Cursor 生成的代码 push 了。带着 OpenAI key。幸好我们有 git hook 拦住了。

防线

bash 复制代码
# 安装 git-secrets
brew install git-secrets

# 配置项目
cd your-project
git secrets --install
git secrets --register-aws

# 自定义规则:拦截常见 API key 模式
git secrets --add 'sk-[a-zA-Z0-9]{20,}'
git secrets --add 'AIzaSy[a-zA-Z0-9_-]{33}'
git secrets --add 'ghp_[a-zA-Z0-9]{36}'

# pre-commit 自动拦截
git secrets --scan

坑四:useEffect 地狱

AI 对 useEffect 有种执念。你让它写个数据加载组件,它能给你整出一坨嵌套的 effect:

tsx 复制代码
// ❌ AI 的 useEffect 大乱炖
function Dashboard() {
  const [user, setUser] = useState(null);
  const [orders, setOrders] = useState([]);
  const [stats, setStats] = useState(null);

  useEffect(() => {
    fetchUser().then(setUser);
  }, []);

  useEffect(() => {
    if (user) {
      fetchOrders(user.id).then(setOrders);
    }
  }, [user]);

  useEffect(() => {
    if (orders.length > 0) {
      calculateStats(orders).then(setStats);
    }
  }, [orders]);

  useEffect(() => {
    if (stats) {
      document.title = `Dashboard - ${stats.totalRevenue}`;
    }
  }, [stats]);
  // 4 个 effect 链式依赖,竞态条件随时爆炸
}

四个 effect 形成隐式依赖链。用户快速切换页面?竞态。网络慢一点?数据错位。

tsx 复制代码
// ✅ 用 React Query / SWR 替代手写 effect
function Dashboard() {
  const { data: user } = useQuery({
    queryKey: ['user'],
    queryFn: fetchUser,
  });

  const { data: orders = [] } = useQuery({
    queryKey: ['orders', user?.id],
    queryFn: () => fetchOrders(user!.id),
    enabled: !!user,
  });

  const stats = useMemo(
    () => orders.length > 0 ? calculateStatsSync(orders) : null,
    [orders]
  );

  useEffect(() => {
    if (stats) document.title = `Dashboard - ${stats.totalRevenue}`;
  }, [stats]);
}

数据获取交给专业库处理缓存、竞态、重试。同步计算用 useMemouseEffect 只剩真正的副作用。

坑五:错误边界缺失

AI 写的组件几乎从来不加 Error Boundary。一个子组件崩了,整个页面白屏。

tsx 复制代码
// ❌ 裸奔组件树
function App() {
  return (
    <Layout>
      <Sidebar />        {/* 这里崩了 */}
      <MainContent />     {/* 跟着白屏 */}
      <NotificationBar /> {/* 也白屏 */}
    </Layout>
  );
}
tsx 复制代码
// ✅ 关键区域加 Error Boundary
import { ErrorBoundary } from 'react-error-boundary';

function App() {
  return (
    <Layout>
      <ErrorBoundary fallback={<SidebarFallback />}>
        <Sidebar />
      </ErrorBoundary>
      <ErrorBoundary fallback={<ContentError />}>
        <MainContent />
      </ErrorBoundary>
      <ErrorBoundary fallback={null}>
        <NotificationBar />
      </ErrorBoundary>
    </Layout>
  );
}

一个组件崩不影响其他的。通知栏崩了?静默降级。核心内容崩了?显示友好错误页。

自动化防线搭建

光靠 code review 是扛不住 AI 产出速度的。必须上自动化:

1. ESLint 规则(React 专项)

json 复制代码
{
  "rules": {
    "react/jsx-no-constructed-context-values": "error",
    "react/no-unstable-nested-components": "error",
    "react/no-object-type-as-default-prop": "error",
    "react-hooks/exhaustive-deps": "warn",
    "no-secrets/no-secrets": "error"
  }
}

2. Vercel React Best Practices

Vercel 开源了一套 40+ 条 React 性能优化规则(react-best-practices),可以直接喂给 AI Agent 当约束:

bash 复制代码
# 克隆到项目根目录
git clone https://github.com/vercel/react-best-practices .react-rules

# 在 AI Agent 的 system prompt 或 .cursorrules 中引用
echo "Follow rules in .react-rules/" >> .cursorrules

这比事后 review 高效多了 ------ 直接让 AI 在生成时就遵守规则。

3. CI 流水线集成

yaml 复制代码
# .github/workflows/ai-code-check.yml
name: AI Code Quality Gate
on: [pull_request]
jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx eslint src/ --max-warnings 0
      - run: git secrets --scan
      - run: npx tsc --noEmit
      - run: npm test -- --coverage --watchAll=false

PR 过不了这些检查?不管是人写的还是 AI 写的,一律打回。

踩坑记录

写这篇文章过程中总结的几个教训:

  1. 不要让 AI 一次生成整个页面。越大的上下文,AI 越容易「自由发挥」。一个组件一个组件来,每个都 review。
  2. React Strict Mode 必开。它能帮你抓到 80% 的 useEffect 问题。开发环境双重渲染虽然烦,但能暴露很多隐藏 bug。
  3. AI 不会主动告诉你它不懂。它会自信地写出看似合理但逻辑有坑的代码。特别是涉及并发、竞态、状态同步这种,人类直觉很重要。
  4. 不要删 AI 的注释。AI 生成代码时经常带注释,这些注释虽然有时啰嗦,但在 review 时能帮你理解它的意图。等 review 完再清理。

小结

AI 写 React 代码不是洪水猛兽,但也绝不是「生成即可用」。

我们团队现在的做法是:AI 写初稿 → ESLint + git-secrets 自动拦截 → 人工 review 核心逻辑 → 合并。3 个月下来,这套流程跑得还算顺畅,AI 生成的代码采纳率稳定在 70% 左右。

最核心的一句话:把 AI 当初级工程师用,别当架构师用。它干活快,但需要你把关。


如果你也在团队里推 AI 编程,欢迎评论区聊聊你踩过的坑,看看大家的经历有没有重叠 😄

相关推荐
独特的账号2 小时前
前端浏览器插件的开发一步搞定
前端·react.js
Lupino2 天前
烧掉 10 刀 API 费,我才明白小程序虚拟列表根本不用“库”!
react.js·微信小程序
嚴寒2 天前
前端配环境配到崩溃?这个一键脚手架让我少掉了一把头发
前端·react.js·架构
古茗前端团队2 天前
嗯…微信小程序主包又双叒叕不够用了!!!
react.js
寅时码3 天前
React 正在演变为一场不可逆的赛博瘟疫:AI 投毒、编译器迷信与装死的官方
前端·react.js·设计模式
学高数就犯困3 天前
React:一个例子讲清楚 useEffect 和 useReducer
react.js
Wect3 天前
JSX & ReactElement 核心解析
前端·react.js·面试
codingWhat4 天前
手撸一个「能打」的 React Table 组件
前端·javascript·react.js
程序员ys4 天前
前端权限控制设计
前端·vue.js·react.js