AI Agent 能写代码,但"能写"和"写好"是两回事。前端页面尤其如此——代码能跑不代表页面能用。本文通过 50 个真实案例,总结 Agent 生成前端页面最容易翻车的 8 类问题:响应式失效、可访问性缺失、视觉不一致、状态管理混乱、边界情况遗漏、性能陷阱、浏览器兼容性、交互细节。每个问题都附带检测方法和修复建议,帮你在 Agent 生成代码后快速把关。

Lab 010:Agent 生成前端页面,最容易在哪些细节翻车?

AI Agent 能写代码,但"能写"和"写好"是两回事。前端页面尤其如此——代码能跑不代表页面能用。本文通过 50 个真实案例,总结 Agent 生成前端页面最容易翻车的 8 类问题:响应式失效、可访问性缺失、视觉不一致、状态管理混乱、边界情况遗漏、性能陷阱、浏览器兼容性、交互细节。每个问题都附带检测方法和修复建议,帮你在 Agent 生成代码后快速把关。

一、实验背景

1.1 实验设计

我们让 Claude Code、Cursor 和 Copilot 三个 Agent 分别生成 50 个常见的前端组件:

  • 表单类(15 个):登录表单、注册表单、搜索框、筛选器...
  • 列表类(15 个):商品列表、用户列表、评论列表、表格...
  • 弹窗类(10 个):确认弹窗、模态框、抽屉、Toast...
  • 导航类(10 个):导航栏、面包屑、分页、标签页...

然后用 8 个维度检查生成结果,记录翻车点。

1.2 检查维度

yaml
# frontend-quality-checklist.yaml
dimensions:
  - name: "响应式"
    weight: 20%
    checks:
      - "移动端(375px)布局是否正常"
      - "平板(768px)布局是否正常"
      - "桌面(1440px)布局是否正常"
      - "是否有横向滚动条"
      - "字体大小是否自适应"
  
  - name: "可访问性"
    weight: 15%
    checks:
      - "键盘是否可完全操作"
      - "颜色对比度是否达标(4.5:1)"
      - "是否有 aria 标签"
      - "图片是否有 alt 文本"
      - "焦点顺序是否合理"
  
  - name: "视觉一致性"
    weight: 15%
    checks:
      - "间距是否统一(8px 网格)"
      - "颜色是否使用设计系统"
      - "字体大小是否一致"
      - "圆角是否统一"
      - "阴影是否统一"
  
  - name: "状态管理"
    weight: 15%
    checks:
      - "加载态是否处理"
      - "空状态是否处理"
      - "错误态是否处理"
      - "禁用态是否处理"
      - "边界值是否处理"
  
  - name: "边界情况"
    weight: 10%
    checks:
      - "超长文本是否截断"
      - "空数组是否显示空状态"
      - "特殊字符是否正确转义"
      - "并发请求是否处理"
      - "网络断开是否提示"
  
  - name: "性能"
    weight: 10%
    checks:
      - "是否有不必要的重渲染"
      - "大列表是否虚拟滚动"
      - "图片是否懒加载"
      - "是否有内存泄漏"
      - "首屏加载是否 < 3s"
  
  - name: "浏览器兼容"
    weight: 10%
    checks:
      - "Chrome 是否正常"
      - "Firefox 是否正常"
      - "Safari 是否正常"
      - "Edge 是否正常"
      - "移动端浏览器是否正常"
  
  - name: "交互细节"
    weight: 5%
    checks:
      - "hover 效果是否有"
      - "点击反馈是否及时"
      - "动画是否流畅"
      - "滚动是否平滑"
      - "拖拽是否支持"

二、8 大翻车点详解

2.1 响应式失效(翻车率 87%)

问题表现

Agent 生成的页面在桌面端看起来完美,但一放到移动端就崩了:

  • 文字溢出容器
  • 图片被拉伸变形
  • 按钮挤成一团
  • 表格无法横向滚动

典型案例

jsx
// ❌ Agent 生成的代码
<div className="flex gap-4">
  <div className="w-64">侧边栏</div>
  <div className="flex-1">主内容</div>
</div>

// 问题:固定宽度 256px,在移动端会挤压主内容
jsx
// ✅ 修复后的代码
<div className="flex flex-col md:flex-row gap-4">
  <aside className="w-full md:w-64">侧边栏</aside>
  <main className="flex-1 min-w-0">主内容</main>
</div>

// 修复:使用响应式断点,移动端堆叠,桌面端并排

检测方法

javascript
// playwright-responsive-test.js
const viewports = [
  { name: 'mobile', width: 375, height: 667 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1440, height: 900 },
];

for (const viewport of viewports) {
  await page.setViewportSize(viewport);
  await page.screenshot({ 
    path: `screenshot-${viewport.name}.png`,
    fullPage: true 
  });
  
  // 检查是否有横向滚动条
  const hasHorizontalScroll = await page.evaluate(() => {
    return document.documentElement.scrollWidth > document.documentElement.clientWidth;
  });
  
  if (hasHorizontalScroll) {
    console.log(`❌ ${viewport.name} 出现横向滚动条`);
  }
}

为什么会翻车

Agent 训练数据中桌面端代码占 80%+,移动端适配经验不足。而且"响应式"是一个隐性需求,如果 Prompt 中没有明确要求,Agent 默认只做桌面端。

解决方案

在 Prompt 中明确要求:

  • "请使用 Tailwind CSS 的响应式断点"
  • "确保在 375px、768px、1440px 三个断点下布局正常"
  • "移动端优先,使用 flex-col 和 md:flex-row"

2.2 可访问性缺失(翻车率 92%)

问题表现

  • 所有按钮都是 <div onClick>,无法键盘操作
  • 颜色对比度不达标(浅灰文字在白色背景上)
  • 没有 aria 标签,屏幕阅读器无法识别
  • 焦点顺序混乱

典型案例

jsx
// ❌ Agent 生成的代码
<div className="text-gray-300">次要信息</div>
<div onClick={handleClick} className="cursor-pointer">点击我</div>

// 问题:
// 1. 颜色对比度只有 2.1:1(要求 4.5:1)
// 2. div 无法通过键盘聚焦
jsx
// ✅ 修复后的代码
<div className="text-gray-600">次要信息</div>
<button 
  onClick={handleClick}
  className="cursor-pointer focus:outline-none focus:ring-2"
  aria-label="执行操作"
>
  点击我
</button>

// 修复:
// 1. 使用更深的灰色(对比度 5.7:1)
// 2. 使用 button 元素,自动支持键盘
// 3. 添加 aria-label

检测方法

javascript
// playwright-a11y-test.js
import { AxeBuilder } from '@axe-core/playwright';

const accessibilityScanResults = await new AxeBuilder({ page })
  .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
  .analyze();

console.log('可访问性问题:', accessibilityScanResults.violations);

// 检查颜色对比度
const contrastIssues = accessibilityScanResults.violations.filter(
  v => v.id === 'color-contrast'
);

if (contrastIssues.length > 0) {
  console.log(`❌ 发现 ${contrastIssues.length} 个颜色对比度问题`);
}

为什么会翻车

可访问性是"隐性需求",Agent 如果不被明确要求,通常会忽略。而且训练数据中很多代码本身就不符合可访问性标准。

解决方案

在 Prompt 中明确要求:

  • "请确保符合 WCAG 2.1 AA 标准"
  • "所有交互元素必须支持键盘操作"
  • "颜色对比度至少 4.5:1"
  • "使用语义化 HTML(button、input、label)"

或者使用自动化检查工具(axe-core)在 CI 中强制检查。

2.3 视觉不一致(翻车率 78%)

问题表现

  • 间距不统一(有的地方 12px,有的地方 16px)
  • 颜色不统一(使用了硬编码的颜色值)
  • 字体大小不统一(14px、15px、16px 混用)
  • 圆角不统一(有的 4px,有的 8px)

典型案例

jsx
// ❌ Agent 生成的代码
<div style={{ padding: '12px', margin: '8px' }}>
  <h3 style={{ fontSize: '18px', color: '#333' }}>标题</h3>
  <p style={{ fontSize: '14px', color: '#666' }}>内容</p>
</div>

// 问题:
// 1. 使用了内联样式,无法复用
// 2. 颜色是硬编码,没有使用设计系统
// 3. 间距不符合 8px 网格
jsx
// ✅ 修复后的代码
<div className="p-4 m-2 space-y-2">
  <h3 className="text-lg text-gray-800">标题</h3>
  <p className="text-sm text-gray-600">内容</p>
</div>

// 修复:
// 1. 使用 Tailwind CSS 的 utility class
// 2. 使用设计系统的颜色(gray-800、gray-600)
// 3. 间距使用 4 的倍数(p-4 = 16px, m-2 = 8px)

检测方法

javascript
// visual-consistency-check.js
const fs = require('fs');
const code = fs.readFileSync('component.jsx', 'utf-8');

// 检查是否有硬编码颜色
const hardcodedColors = code.match(/#[0-9a-fA-F]{3,6}/g) || [];
if (hardcodedColors.length > 0) {
  console.log(`❌ 发现 ${hardcodedColors.length} 个硬编码颜色`);
}

// 检查是否有内联样式
const inlineStyles = code.match(/style=\{.*?\}/g) || [];
if (inlineStyles.length > 0) {
  console.log(`❌ 发现 ${inlineStyles.length} 个内联样式`);
}

// 检查间距是否符合 8px 网格
const spacingValues = code.match(/(padding|margin).*?(\d+)px/g) || [];
const invalidSpacing = spacingValues.filter(s => {
  const value = parseInt(s.match(/\d+/)[0]);
  return value % 8 !== 0 && value !== 4; // 允许 4px 作为半格
});

if (invalidSpacing.length > 0) {
  console.log(`❌ 发现 ${invalidSpacing.length} 个不符合 8px 网格的间距`);
}

为什么会翻车

Agent 不知道项目的设计系统,只能"猜"颜色和间距。而且如果没有提供设计系统的配置文件,Agent 无法自动遵循。

解决方案

在 CLAUDE.md 或项目规范中明确:

  • "请使用 Tailwind CSS,不要使用内联样式"
  • "颜色请使用设计系统的颜色变量(gray-800、blue-500)"
  • "间距请使用 8px 网格(p-2、p-4、p-6...)"

或者提供设计系统的配置文件(tailwind.config.js)。

2.4 状态管理混乱(翻车率 85%)

问题表现

  • 没有处理加载态(loading)
  • 没有处理空状态(empty)
  • 没有处理错误态(error)
  • 没有处理禁用态(disabled)

典型案例

jsx
// ❌ Agent 生成的代码
function UserList() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);
  
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

// 问题:
// 1. 没有 loading 状态(加载中显示空白)
// 2. 没有 empty 状态(空数组显示空白)
// 3. 没有 error 状态(请求失败无提示)
jsx
// ✅ 修复后的代码
function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch('/api/users')
      .then(res => res.json())
      .then(data => {
        setUsers(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <div className="animate-pulse">加载中...</div>;
  if (error) return <div className="text-red-500">加载失败:{error}</div>;
  if (users.length === 0) return <div className="text-gray-500">暂无数据</div>;
  
  return (
    <ul>
      {users.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

// 修复:完整处理了 loading、error、empty 三种状态

检测方法

javascript
// state-management-check.js
const fs = require('fs');
const code = fs.readFileSync('component.jsx', 'utf-8');

// 检查是否有 useState 管理 loading 状态
const hasLoadingState = code.includes('loading') && code.includes('useState');
if (!hasLoadingState) {
  console.log('❌ 缺少 loading 状态管理');
}

// 检查是否有 error 状态处理
const hasErrorHandling = code.includes('error') && code.includes('catch');
if (!hasErrorHandling) {
  console.log('❌ 缺少 error 状态处理');
}

// 检查是否有 empty 状态处理
const hasEmptyState = code.includes('length === 0') || code.includes('!data');
if (!hasEmptyState) {
  console.log('❌ 缺少 empty 状态处理');
}

为什么会翻车

Agent 倾向于生成"理想路径"的代码,假设一切都会成功。但真实场景中,网络会超时、数据会为空、接口会报错。

解决方案

在 Prompt 中明确要求:

  • "请完整处理 loading、error、empty 三种状态"
  • "请使用 Suspense 和 ErrorBoundary 处理异步"
  • "请参考项目中的 UserList.tsx 实现"

或者提供状态管理的模板代码。

2.5 边界情况遗漏(翻车率 89%)

问题表现

  • 超长文本溢出(用户名 100 个字符)
  • 特殊字符未转义(<script>alert('xss')</script>
  • 空数组没有处理
  • 并发请求没有取消

典型案例

jsx
// ❌ Agent 生成的代码
function UserProfile({ user }) {
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

// 问题:
// 1. 如果 user.name 有 100 个字符,会撑破容器
// 2. 如果 user.bio 包含 HTML,会有 XSS 风险
jsx
// ✅ 修复后的代码
function UserProfile({ user }) {
  return (
    <div className="max-w-md">
      <h2 className="text-xl font-bold truncate" title={user.name}>
        {user.name}
      </h2>
      <p className="text-gray-600 line-clamp-3">
        {user.bio}
      </p>
    </div>
  );
}

// 修复:
// 1. 使用 truncate 截断超长文本
// 2. 使用 line-clamp-3 限制行数
// 3. React 自动转义 JSX 中的文本,防止 XSS

检测方法

javascript
// edge-case-test.js
// 测试超长文本
const longText = 'a'.repeat(200);
await page.fill('[name="username"]', longText);
const usernameWidth = await page.$eval('[data-testid="username"]', el => el.offsetWidth);
if (usernameWidth > 500) {
  console.log('❌ 超长文本导致容器撑破');
}

// 测试特殊字符
const specialChars = '<script>alert("xss")</script>';
await page.fill('[name="bio"]', specialChars);
const hasScript = await page.$('script');
if (hasScript) {
  console.log('❌ XSS 漏洞:特殊字符未转义');
}

// 测试空数组
await page.route('/api/users', route => route.fulfill({ 
  status: 200, 
  body: JSON.stringify([]) 
}));
await page.reload();
const hasEmptyState = await page.$('text=暂无数据');
if (!hasEmptyState) {
  console.log('❌ 空数组没有处理');
}

为什么会翻车

Agent 默认假设"数据都是正常的",不会主动考虑边界情况。除非 Prompt 中明确要求,否则不会处理。

解决方案

在 Prompt 中明确要求:

  • "请考虑超长文本(100+ 字符)的截断"
  • "请处理特殊字符(HTML、SQL 注入)"
  • "请处理空数组、null、undefined"
  • "请参考项目中的边界处理规范"

2.6 性能陷阱(翻车率 72%)

问题表现

  • 大列表没有虚拟滚动(1000 条数据渲染卡顿)
  • 图片没有懒加载(首屏加载 10 秒)
  • 不必要的重渲染(每次父组件更新,子组件都重新渲染)
  • 内存泄漏(组件卸载后还在监听事件)

典型案例

jsx
// ❌ Agent 生成的代码
function ProductList({ products }) {
  return (
    <div>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

// 问题:如果 products 有 1000 条,会一次性渲染 1000 个 ProductCard,导致卡顿
jsx
// ✅ 修复后的代码
import { FixedSizeList } from 'react-window';

function ProductList({ products }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={products.length}
      itemSize={100}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <ProductCard product={products[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}

// 修复:使用虚拟滚动,只渲染可见区域的组件

检测方法

javascript
// performance-test.js
// 测试大列表渲染
const startTime = Date.now();
await page.evaluate(() => {
  const products = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Product ${i}` }));
  window.renderProducts(products);
});
const renderTime = Date.now() - startTime;

if (renderTime > 1000) {
  console.log(`❌ 大列表渲染耗时 ${renderTime}ms(应该 < 500ms)`);
}

// 测试不必要的重渲染
const renderCount = await page.evaluate(() => {
  let count = 0;
  const originalRender = window.ProductCard.prototype.render;
  window.ProductCard.prototype.render = function() {
    count++;
    return originalRender.call(this);
  };
  window.forceUpdate();
  return count;
});

if (renderCount > 10) {
  console.log(`❌ 子组件重渲染 ${renderCount} 次(应该 < 5 次)`);
}

为什么会翻车

Agent 不知道数据量会有多大,默认按"少量数据"生成代码。而且性能优化需要额外的库(react-window、react-lazyload),Agent 不会主动引入。

解决方案

在 Prompt 中明确要求:

  • "列表可能有 1000+ 条数据,请使用虚拟滚动"
  • "图片请使用懒加载"
  • "请使用 React.memo 避免不必要的重渲染"

或者提供性能优化的模板代码。

2.7 浏览器兼容性(翻车率 65%)

问题表现

  • 使用了 CSS Grid,但 Safari 11 不支持
  • 使用了 Optional Chaining(?.),但 IE 11 不支持
  • 使用了 backdrop-filter,但 Firefox 旧版本不支持

典型案例

jsx
// ❌ Agent 生成的代码
<div className="backdrop-blur-md bg-white/50">
  毛玻璃效果
</div>

// 问题:backdrop-filter 在 Safari 14 以下不支持
jsx
// ✅ 修复后的代码
<div className="bg-white/90 backdrop-blur-md supports-backdrop-blur:bg-white/50">
  毛玻璃效果
</div>

// 修复:使用渐进增强,不支持时使用降级方案

检测方法

javascript
// browser-compat-test.js
// 使用 BrowserStack 测试多浏览器
const browsers = [
  { browser: 'chrome', version: 'latest' },
  { browser: 'firefox', version: 'latest' },
  { browser: 'safari', version: '14' },
  { browser: 'edge', version: 'latest' },
];

for (const browser of browsers) {
  const driver = await buildDriver(browser);
  await driver.get('http://localhost:3000');
  
  // 检查页面是否正常渲染
  const hasError = await driver.findElement(By.css('.error')).catch(() => null);
  if (hasError) {
    console.log(`❌ ${browser.browser} ${browser.version} 渲染失败`);
  }
  
  await driver.quit();
}

为什么会翻车

Agent 默认使用最新的 CSS 和 JS 特性,不会考虑兼容性。除非明确要求支持旧浏览器,否则不会降级。

解决方案

在 CLAUDE.md 中明确:

  • "项目需要支持 Chrome 90+、Safari 14+、Firefox 90+"
  • "请使用 Autoprefixer 自动添加浏览器前缀"
  • "请使用 Babel 转译新语法"

或者提供 browserslist 配置。

2.8 交互细节(翻车率 58%)

问题表现

  • 按钮没有 hover 效果
  • 点击后没有 loading 反馈
  • 动画不流畅(卡顿或闪烁)
  • 滚动不平滑

典型案例

jsx
// ❌ Agent 生成的代码
<button onClick={handleSubmit}>提交</button>

// 问题:没有 hover 效果,点击后没有反馈
jsx
// ✅ 修复后的代码
<button 
  onClick={handleSubmit}
  disabled={loading}
  className="bg-blue-500 hover:bg-blue-600 active:bg-blue-700 
             transition-colors duration-200 
             disabled:opacity-50 disabled:cursor-not-allowed"
>
  {loading ? (
    <>
      <Spinner className="animate-spin" />
      提交中...
    </>
  ) : '提交'}
</button>

// 修复:
// 1. 添加 hover、active 效果
// 2. 添加 transition 过渡动画
// 3. 禁用时显示 loading 状态

检测方法

javascript
// interaction-test.js
// 测试 hover 效果
const button = await page.$('button');
const beforeHover = await button.screenshot();
await button.hover();
const afterHover = await button.screenshot();

// 比较截图,检查颜色是否变化
const pixelDiff = compareImages(beforeHover, afterHover);
if (pixelDiff < 0.01) {
  console.log('❌ 按钮没有 hover 效果');
}

// 测试点击反馈
await button.click();
const isLoading = await page.$('.spinner');
if (!isLoading) {
  console.log('❌ 点击后没有 loading 反馈');
}

为什么会翻车

交互细节是"锦上添花"的需求,Agent 如果不被明确要求,通常只做"能用"的版本。

解决方案

在 Prompt 中明确要求:

  • "请添加 hover、active 效果"
  • "请使用 transition 添加过渡动画"
  • "点击后请显示 loading 状态"

或者提供交互设计的规范文档。

三、翻车率统计

翻车类型 翻车率 严重程度 检测难度
可访问性缺失 92% 中(需要工具)
边界情况遗漏 89% 低(手动测试)
状态管理混乱 85% 低(手动测试)
响应式失效 87% 低(手动测试)
视觉不一致 78% 中(需要工具)
性能陷阱 72% 高(需要性能分析)
浏览器兼容 65% 高(需要多浏览器测试)
交互细节 58% 低(手动测试)

四、自动化检测方案

4.1 Playwright 截图测试

javascript
// playwright-screenshot-test.js
const { test, expect } = require('@playwright/test');

test.describe('前端质量检查', () => {
  const viewports = [
    { name: 'mobile', width: 375, height: 667 },
    { name: 'tablet', width: 768, height: 1024 },
    { name: 'desktop', width: 1440, height: 900 },
  ];
  
  for (const viewport of viewports) {
    test(`${viewport.name} 响应式检查`, async ({ page }) => {
      await page.setViewportSize(viewport);
      await page.goto('http://localhost:3000');
      
      // 截图对比
      await expect(page).toHaveScreenshot(`${viewport.name}.png`, {
        maxDiffPixels: 100,
      });
      
      // 检查横向滚动条
      const hasHorizontalScroll = await page.evaluate(() => {
        return document.documentElement.scrollWidth > document.documentElement.clientWidth;
      });
      expect(hasHorizontalScroll).toBeFalsy();
    });
  }
  
  test('可访问性检查', async ({ page }) => {
    await page.goto('http://localhost:3000');
    
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();
    
    expect(accessibilityScanResults.violations).toEqual([]);
  });
  
  test('状态检查', async ({ page }) => {
    // 测试 loading 状态
    await page.route('/api/users', route => route.abort());
    await page.goto('http://localhost:3000');
    await expect(page.locator('.loading')).toBeVisible();
    
    // 测试 error 状态
    await page.route('/api/users', route => route.fulfill({ status: 500 }));
    await page.reload();
    await expect(page.locator('.error')).toBeVisible();
    
    // 测试 empty 状态
    await page.route('/api/users', route => route.fulfill({ 
      status: 200, 
      body: JSON.stringify([]) 
    }));
    await page.reload();
    await expect(page.locator('.empty')).toBeVisible();
  });
});

4.2 视觉回归测试

javascript
// visual-regression-test.js
const { compareImages } = require('resemblejs');

async function visualRegressionTest(page, componentName) {
  // 截取当前版本
  const currentScreenshot = await page.screenshot();
  
  // 加载基线版本
  const baselineScreenshot = fs.readFileSync(`baseline/${componentName}.png`);
  
  // 比较差异
  const result = await compareImages(currentScreenshot, baselineScreenshot);
  
  if (result.misMatchPercentage > 5) {
    console.log(`❌ ${componentName} 视觉差异 ${result.misMatchPercentage}%`);
    fs.writeFileSync(`diff/${componentName}.png`, result.getBuffer());
  }
}

4.3 性能监控

javascript
// performance-monitor.js
const { PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 1000) {
      console.log(`❌ 慢操作:${entry.name} 耗时 ${entry.duration}ms`);
    }
  }
});

obs.observe({ entryTypes: ['measure'] });

// 在组件中使用
performance.mark('render-start');
// ... 渲染组件
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');

五、最佳实践总结

5.1 Prompt 模板

markdown
请帮我生成一个 [组件名称],要求:

1. **响应式**   - 使用 Tailwind CSS 的响应式断点
   - 确保在 375px、768px、1440px 三个断点下布局正常
   - 移动端优先,使用 flex-col 和 md:flex-row

2. **可访问性**   - 符合 WCAG 2.1 AA 标准
   - 所有交互元素支持键盘操作
   - 颜色对比度至少 4.5:1
   - 使用语义化 HTML(button、input、label)

3. **状态管理**   - 完整处理 loading、error、empty 三种状态
   - 使用 Suspense 和 ErrorBoundary

4. **边界情况**   - 超长文本截断(使用 truncate 或 line-clamp)
   - 特殊字符转义(防止 XSS)
   - 空数组显示空状态

5. **性能**   - 大列表使用虚拟滚动(react-window)
   - 图片懒加载(react-lazyload)
   - 使用 React.memo 避免不必要的重渲染

6. **视觉**   - 使用 Tailwind CSS,不要内联样式
   - 颜色使用设计系统(gray-800、blue-500)
   - 间距使用 8px 网格(p-2、p-4、p-6)

7. **交互**   - 添加 hover、active 效果
   - 使用 transition 添加过渡动画
   - 点击后显示 loading 状态

请参考项目中的 [参考组件] 实现。

5.2 检查清单

yaml
# frontend-review-checklist.yaml
review_items:
  - category: "响应式"
    items:
      - "移动端(375px)布局正常"
      - "平板(768px)布局正常"
      - "桌面(1440px)布局正常"
      - "无横向滚动条"
  
  - category: "可访问性"
    items:
      - "键盘可完全操作"
      - "颜色对比度达标"
      - "有 aria 标签"
      - "焦点顺序合理"
  
  - category: "状态管理"
    items:
      - "loading 状态已处理"
      - "error 状态已处理"
      - "empty 状态已处理"
  
  - category: "边界情况"
    items:
      - "超长文本已截断"
      - "特殊字符已转义"
      - "空数组已处理"
  
  - category: "性能"
    items:
      - "大列表已虚拟滚动"
      - "图片已懒加载"
      - "无不必要的重渲染"
  
  - category: "视觉"
    items:
      - "间距统一(8px 网格)"
      - "颜色统一(设计系统)"
      - "无内联样式"
  
  - category: "交互"
    items:
      - "有 hover 效果"
      - "点击有反馈"
      - "动画流畅"

六、真实经验与踩坑

6.1 第一次翻车:登录表单在移动端无法提交

场景:Agent 生成了一个登录表单,桌面端测试完美,但发到手机上后"登录"按钮点不了。

原因:按钮被键盘遮挡,用户看不到按钮。

教训

  • 移动端测试必须考虑虚拟键盘
  • 表单底部要留足空间(至少 200px)
  • 使用 position: sticky 固定按钮

修复

jsx
// ✅ 修复后的代码
<div className="pb-32"> {/* 留出键盘空间 */}
  <form>
    {/* 表单内容 */}
  </form>
  
  <div className="fixed bottom-0 left-0 right-0 bg-white p-4 shadow-lg">
    <button className="w-full">登录</button>
  </div>
</div>

6.2 第二次翻车:商品列表在 Safari 卡顿

场景:商品列表在 Chrome 流畅,但在 Safari 卡顿严重。

原因:使用了 CSS Grid 的 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)),Safari 11 不支持。

教训

  • CSS Grid 的 auto-fill/auto-fit 在旧 Safari 有 bug
  • 使用 Flexbox 作为降级方案
  • 使用 @supports 做特性检测

修复

jsx
// ✅ 修复后的代码
<div className="flex flex-wrap gap-4">
  {products.map(product => (
    <div className="w-full sm:w-[calc(50%-0.5rem)] lg:w-[calc(33.33%-1rem)]">
      <ProductCard product={product} />
    </div>
  ))}
</div>

6.3 第三次翻车:搜索框输入卡顿

场景:搜索框每输入一个字符就发请求,导致卡顿。

原因:没有做防抖(debounce),每次 onChange 都触发请求。

教训

  • 输入框必须做防抖(300-500ms)
  • 使用 useDebouncedValue hook
  • 或者使用 AbortController 取消上一次请求

修复

jsx
// ✅ 修复后的代码
import { useDebouncedValue } from '@/hooks/useDebouncedValue';

function SearchBox() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebouncedValue(query, 500);
  
  useEffect(() => {
    if (debouncedQuery) {
      fetchSearchResults(debouncedQuery);
    }
  }, [debouncedQuery]);
  
  return (
    <input 
      value={query} 
      onChange={e => setQuery(e.target.value)} 
    />
  );
}

七、总结

Agent 生成前端代码的最大问题不是"写不出来",而是"写不周全"。它会写出"能跑"的代码,但不会主动考虑响应式、可访问性、边界情况这些"隐性需求"。

核心经验

  1. Prompt 要明确:不要假设 Agent 知道你的规范,把要求写清楚
  2. 自动化检测:用 Playwright、axe-core 等工具自动检查
  3. 人工把关:自动化检测不能覆盖所有场景,关键页面必须人工 Review
  4. 提供参考:给 Agent 看项目中的优秀实现,比口头描述更有效

最终建议

Agent 适合生成"初稿",但"终稿"必须经过自动化检测 + 人工 Review。把 Agent 当作"初级开发者",它的代码需要你把关。

八、系列导航

上一篇:案例 005:一个 20 人团队的 Agent 落地 30 天复盘 下一篇:Lab 011:Agent 做大型重构的上限在哪里?