27b - 组件生成策略
本文是《AI Agent 实战手册》第 27 章第 2 节。 上一节:27a-AI辅助前端开发概览 | 下一节:27c-设计到代码工作流
概述
组件是现代前端开发的基本构建单元,也是 AI 代码生成最擅长的领域。本节系统化地覆盖 React、Vue、Svelte 三大框架的组件生成策略,包括函数组件、Hooks/Composables/Runes、状态管理和样式方案的 Prompt 模式,帮助开发者建立一套可复用的 AI 组件生成方法论。无论你使用哪个框架,都能找到对应的最佳实践和即用型提示词模板。
1. 组件生成基础理论
1.1 为什么组件是 AI 生成的最佳粒度
前端组件具有几个特征,使其成为 AI 代码生成的理想目标:
| 特征 | 说明 | 对 AI 生成的影响 |
|---|---|---|
| 自包含性 | 组件封装了模板、逻辑和样式 | AI 可以在一次生成中产出完整可用的代码单元 |
| 明确的接口 | Props/Events 定义了组件的输入输出 | AI 可以从接口定义推断实现细节 |
| 可视化验证 | 组件渲染为可见的 UI | 开发者可以立即判断生成质量 |
| 丰富的训练数据 | GitHub 上有海量开源组件 | AI 对常见组件模式有深度理解 |
| Token 友好 | 单个组件通常在 200-500 行以内 | 不超出 AI 的有效生成窗口 |
1.2 组件生成的四层模型
AI 组件生成可以分为四个层次,每层对应不同的 Prompt 策略:
┌─────────────────────────────────────────────────┐
│ 第四层:组件组合与页面组装 │
│ 多个组件协同工作,数据流、路由、布局 │
├─────────────────────────────────────────────────┤
│ 第三层:状态管理与数据流 │
│ 全局状态、服务端状态、组件间通信 │
├─────────────────────────────────────────────────┤
│ 第二层:交互逻辑与副作用 │
│ 事件处理、API 调用、动画、表单验证 │
├─────────────────────────────────────────────────┤
│ 第一层:UI 结构与样式 │
│ HTML 结构、CSS 样式、响应式布局、无障碍 │
└─────────────────────────────────────────────────┘关键原则:从底层向上逐层生成,每层验证后再进入下一层。不要试图让 AI 一次生成包含复杂状态管理和多组件交互的完整页面。
1.3 组件生成工具选择矩阵
| 工具 | 最佳场景 | 支持框架 | 价格 | AI 生成质量 |
|---|---|---|---|---|
| v0.dev | UI 组件原型、shadcn/ui 风格 | React | 免费(5 credits/天)/ $20/月 | ⭐⭐⭐⭐⭐ |
| Cursor Composer | 多文件组件开发、重构 | React/Vue/Svelte/Angular | $20/月(Pro) | ⭐⭐⭐⭐⭐ |
| Claude Code | 复杂组件、全项目理解 | React/Vue/Svelte/Angular | $20/月(Max 5x) | ⭐⭐⭐⭐⭐ |
| Kiro | Spec 驱动组件开发 | React/Vue/Svelte | 免费(预览期) | ⭐⭐⭐⭐ |
| Bolt.new | 快速全栈原型 | React/Vue/Svelte | 免费 / $20/月 | ⭐⭐⭐⭐ |
| GitHub Copilot | 行级补全、内联生成 | 所有框架 | $10/月 | ⭐⭐⭐⭐ |
| Windsurf | 类 Cursor 体验、预算友好 | React/Vue/Svelte | 免费 / $15/月 | ⭐⭐⭐⭐ |
1.4 通用 Prompt 结构框架
无论使用哪个框架,高质量的组件生成 Prompt 都应包含以下结构:
## 组件:[组件名称]
### 1. 功能描述
[2-3 句话描述组件用途]
### 2. 技术约束
- 框架:[React/Vue/Svelte] + [版本]
- 语言:TypeScript
- 样式:[Tailwind/CSS Modules/styled-components]
- 组件库:[shadcn/ui/Vuetify/Skeleton UI/无]
### 3. Props 接口
[列出所有 props 及其类型]
### 4. 状态需求
[描述内部状态和外部状态依赖]
### 5. 交互行为
[描述用户交互和状态变化]
### 6. 视觉规范
[尺寸、颜色、间距、响应式行为]
### 7. 无障碍要求
[ARIA 角色、键盘导航、屏幕阅读器支持]
### 8. 测试要求
[需要覆盖的测试场景]2. React 组件生成策略
2.1 React 组件生成的优势与挑战
React 是 AI 代码生成支持最好的前端框架,原因在于:
优势:
- 训练数据最丰富——GitHub 上 React 项目数量远超其他框架
- 函数组件 + JSX 的模式简单直观,AI 生成准确率高
- shadcn/ui + Tailwind CSS 生态成熟,AI 工具原生支持
- TypeScript 类型系统帮助 AI 生成更准确的代码
挑战:
- Hooks 规则(不能在条件语句中调用)AI 偶尔违反
- Server Component vs Client Component 边界容易混淆
- 闭包陷阱(stale closure)在复杂状态逻辑中出现
- 过度使用 useEffect 是 AI 生成代码的常见问题
2.2 函数组件生成模式
基础函数组件 Prompt
生成一个 React 函数组件 [ComponentName]:
## 技术栈
- React 19 + TypeScript
- Tailwind CSS 4
- shadcn/ui 组件库
## Props 接口
interface [ComponentName]Props {
[propName]: [type]; // [描述]
[propName]?: [type]; // [描述](可选,默认值:[值])
}
## 功能描述
[详细描述组件功能]
## 变体
- size: "sm" | "md" | "lg"
- variant: "[variant1]" | "[variant2]"
## 要求
1. 使用 named export
2. Props 使用解构 + 默认值
3. 使用 cn() 工具函数合并 className
4. 支持 className prop 透传
5. 使用 forwardRef 支持 ref 转发
6. 包含 displayName 设置实际示例:生成 UserCard 组件
生成一个 React 函数组件 UserCard:
## 技术栈
- React 19 + TypeScript
- Tailwind CSS 4
- shadcn/ui(Card, Avatar, Badge)
## Props 接口
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
avatar?: string;
role: "admin" | "editor" | "viewer";
isOnline: boolean;
};
onEdit?: (userId: string) => void;
onDelete?: (userId: string) => void;
className?: string;
}
## 功能描述
显示用户信息卡片,包含头像、姓名、邮箱、角色标签和在线状态指示器。
支持编辑和删除操作按钮。
## 视觉规范
- 卡片圆角 12px,带轻微阴影
- 头像 48px 圆形,无头像时显示姓名首字母
- 角色标签颜色:admin=红色, editor=蓝色, viewer=灰色
- 在线状态:绿色圆点(在线)/ 灰色圆点(离线)
- 悬停时卡片阴影加深
## 无障碍
- 卡片使用 article 标签
- 操作按钮有 aria-label
- 在线状态有 sr-only 文本描述AI 生成的参考代码结构:
import { forwardRef } from "react";
import { Card, CardContent } from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Pencil, Trash2 } from "lucide-react";
import { cn } from "@/lib/utils";
interface UserCardProps {
user: {
id: string;
name: string;
email: string;
avatar?: string;
role: "admin" | "editor" | "viewer";
isOnline: boolean;
};
onEdit?: (userId: string) => void;
onDelete?: (userId: string) => void;
className?: string;
}
const roleConfig = {
admin: { label: "管理员", variant: "destructive" as const },
editor: { label: "编辑者", variant: "default" as const },
viewer: { label: "查看者", variant: "secondary" as const },
};
export const UserCard = forwardRef<HTMLElement, UserCardProps>(
({ user, onEdit, onDelete, className }, ref) => {
const initials = user.name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase();
return (
<Card
ref={ref}
className={cn(
"transition-shadow hover:shadow-md",
className
)}
>
<CardContent className="flex items-center gap-4 p-4">
<div className="relative">
<Avatar className="h-12 w-12">
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
<span
className={cn(
"absolute bottom-0 right-0 h-3 w-3 rounded-full border-2 border-white",
user.isOnline ? "bg-green-500" : "bg-gray-300"
)}
>
<span className="sr-only">
{user.isOnline ? "在线" : "离线"}
</span>
</span>
</div>
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{user.name}</p>
<p className="text-sm text-muted-foreground truncate">
{user.email}
</p>
</div>
<Badge variant={roleConfig[user.role].variant}>
{roleConfig[user.role].label}
</Badge>
<div className="flex gap-1">
{onEdit && (
<Button
variant="ghost"
size="icon"
onClick={() => onEdit(user.id)}
aria-label={`编辑 ${user.name}`}
>
<Pencil className="h-4 w-4" />
</Button>
)}
{onDelete && (
<Button
variant="ghost"
size="icon"
onClick={() => onDelete(user.id)}
aria-label={`删除 ${user.name}`}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
</CardContent>
</Card>
);
}
);
UserCard.displayName = "UserCard";2.3 Hooks 生成模式
React Hooks 是 AI 生成中最容易出错的部分。以下是针对不同 Hook 类型的 Prompt 策略。
自定义 Hook 生成 Prompt
生成一个自定义 React Hook:use[HookName]
## 功能
[描述 Hook 的用途和返回值]
## 接口
function use[HookName]([params]): {
[returnValue1]: [type];
[returnValue2]: [type];
[action1]: ([params]) => [returnType];
}
## 内部状态
- [state1]: [type] — [描述]
- [state2]: [type] — [描述]
## 副作用
- [描述需要的 useEffect 及其依赖]
## 清理逻辑
- [描述组件卸载时需要清理的资源]
## 错误处理
- [描述错误状态和恢复策略]
## 约束
1. 遵循 Hooks 规则(不在条件/循环中调用)
2. 正确设置 useEffect 依赖数组
3. 使用 useCallback 包裹返回的函数
4. 使用 useRef 存储不需要触发重渲染的值
5. 包含 TypeScript 泛型支持(如适用)实际示例:生成 useDebounce Hook
生成一个自定义 React Hook:useDebounce
## 功能
对输入值进行防抖处理,返回防抖后的值。
支持自定义延迟时间和立即执行选项。
## 接口
function useDebounce<T>(value: T, delay?: number): T;
## 要求
1. 默认延迟 300ms
2. 组件卸载时清除定时器
3. delay 变化时重置定时器
4. 支持泛型类型
5. 包含 JSDoc 注释AI 生成的参考代码:
import { useState, useEffect } from "react";
/**
* 对输入值进行防抖处理
* @param value - 需要防抖的值
* @param delay - 防抖延迟时间(毫秒),默认 300ms
* @returns 防抖后的值
*
* @example
* const [search, setSearch] = useState("");
* const debouncedSearch = useDebounce(search, 500);
*
* useEffect(() => {
* // 仅在用户停止输入 500ms 后执行搜索
* fetchResults(debouncedSearch);
* }, [debouncedSearch]);
*/
export function useDebounce<T>(value: T, delay = 300): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}常见 Hooks 生成场景与 Prompt 要点
| Hook 场景 | Prompt 关键要点 | AI 常见错误 | 修正策略 |
|---|---|---|---|
| 数据获取(useFetch) | 明确加载/错误/成功状态、取消请求、缓存策略 | 缺少 AbortController 清理、竞态条件 | 要求使用 AbortController,或建议使用 TanStack Query |
| 表单管理(useForm) | 明确验证规则、提交处理、字段注册方式 | 过度使用 useState、缺少验证 | 建议使用 React Hook Form + Zod |
| 本地存储(useLocalStorage) | 明确序列化/反序列化、SSR 兼容、类型安全 | SSR 环境下访问 window 报错 | 要求添加 typeof window 检查 |
| 媒体查询(useMediaQuery) | 明确断点值、SSR 默认值、事件监听清理 | 缺少 SSR 兼容、内存泄漏 | 要求使用 matchMedia API + cleanup |
| 无限滚动(useInfiniteScroll) | 明确触发阈值、加载状态、IntersectionObserver | 使用 scroll 事件而非 IntersectionObserver | 明确要求使用 IntersectionObserver |
| 动画(useAnimation) | 明确动画类型、时长、缓动函数 | 使用 setTimeout 而非 requestAnimationFrame | 要求使用 Web Animations API 或 Framer Motion |
2.4 React TypeScript Props 模式
Props 类型定义的 Prompt 策略
为以下 React 组件定义 TypeScript Props 接口:
## 组件描述
[描述组件功能]
## Props 要求
1. 必需 props:[列出]
2. 可选 props(带默认值):[列出]
3. 事件回调 props:[列出]
4. 子组件 props:[children 类型]
5. 样式透传:className, style
6. HTML 属性透传:使用 ComponentPropsWithoutRef<"[element]">
## 类型约束
- 使用 discriminated union 处理互斥 props
- 使用泛型处理动态类型
- 使用 Omit/Pick 复用已有类型高级 Props 模式示例
// 1. 多态组件(as prop)
interface ButtonProps<T extends React.ElementType = "button"> {
as?: T;
variant: "primary" | "secondary" | "ghost";
size: "sm" | "md" | "lg";
isLoading?: boolean;
children: React.ReactNode;
}
type PolymorphicButtonProps<T extends React.ElementType = "button"> =
ButtonProps<T> &
Omit<React.ComponentPropsWithoutRef<T>, keyof ButtonProps<T>>;
// 2. 判别联合(Discriminated Union)
type NotificationProps =
| { type: "success"; message: string; onDismiss?: () => void }
| { type: "error"; message: string; error: Error; onRetry: () => void }
| { type: "loading"; message?: string };
// 3. 泛型组件
interface SelectProps<T> {
options: T[];
value: T | null;
onChange: (value: T) => void;
getLabel: (item: T) => string;
getValue: (item: T) => string;
placeholder?: string;
}2.5 Next.js Server Component vs Client Component 生成
在 Next.js App Router 中,AI 需要正确区分 Server Component 和 Client Component。
Server Component 生成 Prompt
生成一个 Next.js Server Component:[ComponentName]
## 约束(Server Component 规则)
- 不使用 useState, useEffect, useRef 等 React hooks
- 不使用浏览器 API(window, document, localStorage)
- 不使用事件处理器(onClick, onChange 等)
- 可以直接 async/await 获取数据
- 可以直接访问数据库、文件系统、环境变量
- 不添加 'use client' 指令
## 数据获取
[描述需要获取的数据和来源]
## 渲染
[描述 UI 结构]Client Component 生成 Prompt
生成一个 Next.js Client Component:[ComponentName]
## 约束(Client Component 规则)
- 文件顶部添加 'use client' 指令
- 可以使用 React hooks 和浏览器 API
- 可以使用事件处理器
- 不能直接访问数据库或文件系统
- 数据通过 props 从 Server Component 传入,或通过 API 获取
## 交互逻辑
[描述需要的客户端交互]
## 状态管理
[描述组件内部状态]SC/CC 边界决策表
| 需求 | 组件类型 | 原因 |
|---|---|---|
| 显示数据库查询结果 | Server Component | 直接访问数据源,减少客户端 JS |
| 搜索输入框 + 实时过滤 | Client Component | 需要 useState 和 onChange |
| 静态导航栏 | Server Component | 无交互需求 |
| 带下拉菜单的导航栏 | 混合:外层 SC + 下拉菜单 CC | 最小化 ‘use client’ 范围 |
| 数据表格 + 排序/筛选 | Client Component | 需要交互状态 |
| 用户头像 + 在线状态 | Client Component | 需要实时更新 |
| Markdown 渲染 | Server Component | 纯渲染,无交互 |
| 表单提交 | Client Component | 需要事件处理和验证 |
3. Vue 组件生成策略
3.1 Vue 3 组件生成的特点
Vue 3 的单文件组件(SFC)格式对 AI 生成有独特的优势和挑战:
优势:
- SFC 将模板、脚本、样式组织在一个文件中,结构清晰
<script setup>语法简洁,减少样板代码- Composition API 的 composable 模式与 React Hooks 类似,AI 迁移知识容易
- TypeScript 支持通过
defineProps<T>()泛型语法实现,类型推断优秀
挑战:
- AI 有时混淆 Options API 和 Composition API
<script setup>的宏(defineProps、defineEmits、defineExpose)AI 偶尔使用错误- Vue 的模板语法(v-if、v-for、v-model)与 JSX 差异大,AI 从 React 知识迁移时可能出错
- Scoped CSS 的
:deep()选择器 AI 经常遗忘
3.2 Vue SFC 组件生成模式
基础 Vue 组件 Prompt
生成一个 Vue 3 单文件组件 [ComponentName].vue:
## 技术栈
- Vue 3.5+ / <script setup> + TypeScript
- 样式方案:[Tailwind CSS / <style scoped>]
- 组件库:[无 / Vuetify / PrimeVue / Naive UI]
## Props
使用 defineProps<T>() 泛型语法定义:
interface [ComponentName]Props {
[propName]: [type]; // [描述]
}
## Emits
使用 defineEmits<T>() 泛型语法定义:
interface [ComponentName]Emits {
(e: '[eventName]', [payload]: [type]): void;
}
## 模板要求
- 使用 Vue 模板语法(v-if, v-for, v-model)
- v-for 必须包含 :key
- 条件渲染使用 v-if/v-else-if/v-else
## 要求
1. 使用 Composition API,不使用 Options API
2. 响应式状态使用 ref() 或 reactive()
3. 计算属性使用 computed()
4. 侦听器使用 watch() 或 watchEffect()
5. 组件命名使用 PascalCase实际示例:生成 Vue SearchFilter 组件
生成一个 Vue 3 单文件组件 SearchFilter.vue:
## 技术栈
- Vue 3.5 + <script setup> + TypeScript
- Tailwind CSS 4
## Props
interface SearchFilterProps {
placeholder?: string; // 搜索框占位文本,默认"搜索..."
filters: FilterOption[]; // 筛选选项列表
modelValue: string; // 搜索关键词(v-model)
activeFilters: string[]; // 当前激活的筛选项(v-model)
}
interface FilterOption {
id: string;
label: string;
icon?: string;
}
## Emits
- update:modelValue — 搜索关键词变化
- update:activeFilters — 筛选项变化
- search — 用户按回车或点击搜索按钮
## 功能
搜索输入框 + 筛选标签组合组件。
输入框支持清除按钮,筛选标签支持多选切换。
## 无障碍
- 搜索输入有 role="searchbox"
- 筛选标签使用 role="checkbox" + aria-checkedAI 生成的参考代码:
<script setup lang="ts">
import { computed } from "vue";
interface FilterOption {
id: string;
label: string;
icon?: string;
}
interface SearchFilterProps {
placeholder?: string;
filters: FilterOption[];
modelValue: string;
activeFilters: string[];
}
const props = withDefaults(defineProps<SearchFilterProps>(), {
placeholder: "搜索...",
});
const emit = defineEmits<{
"update:modelValue": [value: string];
"update:activeFilters": [value: string[]];
search: [];
}>();
const hasValue = computed(() => props.modelValue.length > 0);
function clearSearch() {
emit("update:modelValue", "");
}
function toggleFilter(filterId: string) {
const current = [...props.activeFilters];
const index = current.indexOf(filterId);
if (index === -1) {
current.push(filterId);
} else {
current.splice(index, 1);
}
emit("update:activeFilters", current);
}
function isFilterActive(filterId: string): boolean {
return props.activeFilters.includes(filterId);
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") {
emit("search");
}
}
</script>
<template>
<div class="flex flex-col gap-3">
<!-- 搜索输入框 -->
<div class="relative">
<svg
class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<input
role="searchbox"
type="text"
:value="modelValue"
:placeholder="placeholder"
class="w-full rounded-lg border border-gray-200 bg-white py-2 pl-10 pr-10
text-sm placeholder:text-gray-400
focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
@input="emit('update:modelValue', ($event.target as HTMLInputElement).value)"
@keydown="handleKeydown"
/>
<button
v-if="hasValue"
class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400
hover:text-gray-600"
aria-label="清除搜索"
@click="clearSearch"
>
✕
</button>
</div>
<!-- 筛选标签 -->
<div v-if="filters.length > 0" class="flex flex-wrap gap-2">
<button
v-for="filter in filters"
:key="filter.id"
role="checkbox"
:aria-checked="isFilterActive(filter.id)"
class="rounded-full border px-3 py-1 text-sm transition-colors"
:class="
isFilterActive(filter.id)
? 'border-blue-500 bg-blue-50 text-blue-700'
: 'border-gray-200 bg-white text-gray-600 hover:border-gray-300'
"
@click="toggleFilter(filter.id)"
>
<span v-if="filter.icon" class="mr-1">{{ filter.icon }}</span>
{{ filter.label }}
</button>
</div>
</div>
</template>3.3 Vue Composable 生成模式
Vue 的 Composable 等同于 React 的自定义 Hook,是逻辑复用的核心机制。
Composable 生成 Prompt
生成一个 Vue 3 Composable:use[Name]
## 功能
[描述 Composable 的用途]
## 接口
function use[Name]([params]): {
[returnValue]: Ref<[type]>;
[action]: ([params]) => [returnType];
}
## 响应式状态
- 使用 ref() 定义响应式值
- 使用 computed() 定义派生值
- 使用 readonly() 暴露只读状态
## 生命周期
- onMounted: [描述挂载时的操作]
- onUnmounted: [描述卸载时的清理]
## 要求
1. 返回值使用 ref/computed,保持响应式
2. 副作用在 onUnmounted 中清理
3. 支持 SSR(避免直接访问 window/document)
4. 包含 TypeScript 类型定义实际示例:生成 useCountdown Composable
import { ref, computed, onUnmounted } from "vue";
interface UseCountdownOptions {
/** 倒计时总秒数 */
seconds: number;
/** 是否自动开始 */
autoStart?: boolean;
/** 倒计时结束回调 */
onComplete?: () => void;
}
interface UseCountdownReturn {
/** 剩余秒数 */
remaining: Readonly<Ref<number>>;
/** 是否正在运行 */
isRunning: Readonly<Ref<boolean>>;
/** 格式化的时间字符串 MM:SS */
formatted: ComputedRef<string>;
/** 开始倒计时 */
start: () => void;
/** 暂停倒计时 */
pause: () => void;
/** 重置倒计时 */
reset: () => void;
}
export function useCountdown(options: UseCountdownOptions): UseCountdownReturn {
const { seconds, autoStart = false, onComplete } = options;
const remaining = ref(seconds);
const isRunning = ref(false);
let intervalId: ReturnType<typeof setInterval> | null = null;
const formatted = computed(() => {
const mins = Math.floor(remaining.value / 60);
const secs = remaining.value % 60;
return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
});
function tick() {
if (remaining.value <= 0) {
pause();
onComplete?.();
return;
}
remaining.value--;
}
function start() {
if (isRunning.value) return;
isRunning.value = true;
intervalId = setInterval(tick, 1000);
}
function pause() {
isRunning.value = false;
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
}
function reset() {
pause();
remaining.value = seconds;
}
onUnmounted(() => {
if (intervalId) clearInterval(intervalId);
});
if (autoStart) start();
return {
remaining: readonly(remaining),
isRunning: readonly(isRunning),
formatted,
start,
pause,
reset,
};
}3.4 Vue 与 React 组件生成对比
| 维度 | React | Vue 3 | Prompt 差异 |
|---|---|---|---|
| 组件定义 | 函数返回 JSX | SFC(template + script + style) | Vue 需要指定 SFC 格式 |
| Props 类型 | interface Props {} | defineProps<Props>() | Vue 使用编译器宏 |
| 事件 | 回调函数 props | defineEmits<Emits>() | Vue 有专门的事件系统 |
| 双向绑定 | 受控组件模式 | v-model + defineModel() | Vue 更简洁 |
| 条件渲染 | JSX 三元表达式 | v-if / v-else | 模板语法差异 |
| 列表渲染 | array.map() | v-for | Vue 需要 :key |
| 样式 | className + Tailwind | <style scoped> 或 Tailwind | Vue 有原生 scoped CSS |
| Ref 访问 | useRef() | ref() + template ref | 概念相似但语法不同 |
| 生命周期 | useEffect | onMounted / onUnmounted | Vue 更明确的生命周期钩子 |
Vue 特有的 Prompt 注意事项
- 始终指定
<script setup>:不指定时 AI 可能生成 Options API 代码 - 明确 v-model 用法:Vue 3.4+ 支持
defineModel()宏,比手动 emit 更简洁 - 指定 CSS 方案:Vue 的
<style scoped>和 Tailwind 可以共存,需要明确偏好 - 注意 auto-import:Nuxt 3 项目中 ref、computed 等会自动导入,需要在 Steering 中说明
4. Svelte 组件生成策略
4.1 Svelte 5 Runes 时代的组件生成
Svelte 5 引入了 Runes 系统,彻底改变了响应式编程模型。AI 对 Svelte 的支持正在快速改善,但仍需要精确的 Prompt 引导。
Svelte 5 Runes 核心概念:
| Rune | 用途 | 等价于 |
|---|---|---|
$state | 声明响应式状态 | React useState / Vue ref() |
$derived | 声明派生值 | React useMemo / Vue computed() |
$effect | 声明副作用 | React useEffect / Vue watchEffect() |
$props | 声明组件 props | React props / Vue defineProps() |
$bindable | 声明可双向绑定的 prop | Vue defineModel() |
$inspect | 开发调试用 | React DevTools / Vue DevTools |
4.2 Svelte 5 组件生成模式
基础 Svelte 5 组件 Prompt
生成一个 Svelte 5 组件 [ComponentName].svelte:
## 技术栈
- Svelte 5(使用 Runes 语法)
- TypeScript
- 样式:[Tailwind CSS / <style> scoped CSS]
## Props
使用 $props() rune 定义:
let { [propName], [propName] = [defaultValue] }: {
[propName]: [type];
[propName]?: [type];
} = $props();
## 响应式状态
使用 $state rune:
let [stateName] = $state<[type]>([initialValue]);
## 派生值
使用 $derived rune:
let [derivedName] = $derived([expression]);
## 副作用
使用 $effect rune:
$effect(() => { [副作用逻辑] });
## 重要约束
1. 不使用旧版 $: 响应式声明(Svelte 4 语法)
2. 不使用 export let(Svelte 4 props 语法)
3. 使用 Svelte 5 的 Runes 语法
4. 事件处理使用 on[event] 属性而非 on:event 指令实际示例:生成 Svelte 5 TodoList 组件
生成一个 Svelte 5 组件 TodoList.svelte:
## 技术栈
- Svelte 5 + Runes + TypeScript
- Tailwind CSS
## Props
interface TodoListProps {
title?: string; // 列表标题,默认 "待办事项"
}
## 功能
一个完整的待办事项列表组件:
- 添加新待办项(输入框 + 回车/按钮)
- 切换完成状态(复选框)
- 删除待办项
- 显示完成/总数统计
- 筛选:全部/未完成/已完成
## 无障碍
- 列表使用 ul/li 语义标签
- 复选框有关联的 label
- 删除按钮有 aria-labelAI 生成的参考代码:
<script lang="ts">
interface Todo {
id: string;
text: string;
completed: boolean;
}
type Filter = "all" | "active" | "completed";
let { title = "待办事项" }: { title?: string } = $props();
let todos = $state<Todo[]>([]);
let newTodoText = $state("");
let filter = $state<Filter>("all");
let filteredTodos = $derived(
todos.filter((todo) => {
if (filter === "active") return !todo.completed;
if (filter === "completed") return todo.completed;
return true;
})
);
let stats = $derived({
total: todos.length,
completed: todos.filter((t) => t.completed).length,
active: todos.filter((t) => !t.completed).length,
});
function addTodo() {
const text = newTodoText.trim();
if (!text) return;
todos.push({
id: crypto.randomUUID(),
text,
completed: false,
});
newTodoText = "";
}
function toggleTodo(id: string) {
const todo = todos.find((t) => t.id === id);
if (todo) todo.completed = !todo.completed;
}
function deleteTodo(id: string) {
const index = todos.findIndex((t) => t.id === id);
if (index !== -1) todos.splice(index, 1);
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") addTodo();
}
</script>
<div class="mx-auto max-w-md rounded-lg border bg-white p-6 shadow-sm">
<h2 class="mb-4 text-xl font-semibold">{title}</h2>
<!-- 输入区域 -->
<div class="mb-4 flex gap-2">
<input
type="text"
bind:value={newTodoText}
placeholder="添加新待办..."
class="flex-1 rounded-md border px-3 py-2 text-sm
focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
onkeydown={handleKeydown}
/>
<button
onclick={addTodo}
disabled={!newTodoText.trim()}
class="rounded-md bg-blue-500 px-4 py-2 text-sm font-medium text-white
hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
添加
</button>
</div>
<!-- 筛选标签 -->
<div class="mb-3 flex gap-1">
{#each ["all", "active", "completed"] as f}
<button
onclick={() => (filter = f as Filter)}
class="rounded-full px-3 py-1 text-xs transition-colors"
class:bg-blue-100={filter === f}
class:text-blue-700={filter === f}
class:text-gray-500={filter !== f}
class:hover:bg-gray-100={filter !== f}
>
{f === "all" ? "全部" : f === "active" ? "未完成" : "已完成"}
</button>
{/each}
</div>
<!-- 待办列表 -->
<ul class="space-y-2">
{#each filteredTodos as todo (todo.id)}
<li class="flex items-center gap-3 rounded-md border p-3">
<input
type="checkbox"
id="todo-{todo.id}"
checked={todo.completed}
onchange={() => toggleTodo(todo.id)}
class="h-4 w-4 rounded border-gray-300"
/>
<label
for="todo-{todo.id}"
class="flex-1 text-sm"
class:line-through={todo.completed}
class:text-gray-400={todo.completed}
>
{todo.text}
</label>
<button
onclick={() => deleteTodo(todo.id)}
aria-label="删除 {todo.text}"
class="text-gray-400 hover:text-red-500"
>
✕
</button>
</li>
{/each}
</ul>
<!-- 统计 -->
{#if todos.length > 0}
<p class="mt-3 text-xs text-gray-400">
{stats.completed}/{stats.total} 已完成
</p>
{/if}
</div>4.3 Svelte 5 与 React/Vue 的关键差异
| 维度 | React | Vue 3 | Svelte 5 | AI Prompt 要点 |
|---|---|---|---|---|
| 响应式声明 | useState() | ref() | $state | Svelte 使用编译器魔法,无需导入 |
| 派生值 | useMemo() | computed() | $derived | Svelte 自动追踪依赖 |
| 副作用 | useEffect() | watchEffect() | $effect | Svelte 自动清理,无需返回清理函数 |
| Props | 函数参数解构 | defineProps<T>() | $props() | Svelte 使用解构赋值语法 |
| 事件 | 回调 props | defineEmits | 回调 props | Svelte 5 回归回调模式 |
| 双向绑定 | 受控组件 | v-model | bind:value | Svelte 使用 bind 指令 |
| 条件渲染 | JSX 三元 | v-if | {#if} | Svelte 使用块语法 |
| 列表渲染 | map() | v-for | {#each} | Svelte 支持 keyed each |
| 样式 | className | <style scoped> | <style> (默认 scoped) | Svelte 样式默认组件作用域 |
4.4 Svelte 组件生成的常见 AI 错误
| 错误 | 描述 | 修正 Prompt |
|---|---|---|
使用 $: 语法 | AI 生成 Svelte 4 的响应式声明 | ”使用 Svelte 5 Runes 语法($state, $derived, $effect),不使用 $: 声明” |
使用 export let | AI 生成 Svelte 4 的 props 语法 | ”使用 $props() rune 定义 props,不使用 export let” |
使用 on:click | AI 生成 Svelte 4 的事件语法 | ”使用 onclick 属性而非 on:click 指令(Svelte 5 语法)“ |
使用 createEventDispatcher | AI 生成 Svelte 4 的事件派发 | ”使用回调 props 模式传递事件,不使用 createEventDispatcher” |
| 缺少 TypeScript | AI 生成纯 JS 代码 | ”使用 <script lang='ts'> 并为所有变量添加类型注解” |
| 响应式数组操作错误 | 使用 push() 但不触发更新 | ”Svelte 5 的 $state 数组支持直接 push/splice 触发更新” |
5. 状态管理代码生成
5.1 状态管理方案选择
不同框架有不同的主流状态管理方案,AI 生成时需要明确指定:
| 框架 | 推荐方案 | 适用场景 | 价格 |
|---|---|---|---|
| React | Zustand | 轻量全局状态,简单 API | 开源免费 |
| React | TanStack Query | 服务端状态(API 数据缓存) | 开源免费 |
| React | Jotai | 原子化状态,细粒度更新 | 开源免费 |
| React | Redux Toolkit | 企业级复杂状态,时间旅行调试 | 开源免费 |
| Vue | Pinia | Vue 官方状态管理,DevTools 集成 | 开源免费 |
| Vue | VueUse | 组合式工具函数集合 | 开源免费 |
| Svelte | Svelte Stores | 内置响应式 store | 开源免费(内置) |
| Svelte | $state (Runes) | Svelte 5 内置响应式 | 开源免费(内置) |
5.2 Zustand Store 生成(React)
Zustand Store 生成 Prompt
生成一个 Zustand store:use[StoreName]Store
## 技术栈
- Zustand v5 + TypeScript
- 使用 immer 中间件(如需深层更新)
- 使用 persist 中间件(如需持久化)
## 状态定义
interface [StoreName]State {
// 数据状态
[field]: [type];
// 加载/错误状态
isLoading: boolean;
error: string | null;
// Actions
[actionName]: ([params]) => [returnType];
}
## Actions 描述
1. [actionName]: [描述功能和副作用]
2. [actionName]: [描述功能和副作用]
## 中间件需求
- persist: [是否需要持久化到 localStorage]
- devtools: [是否需要 Redux DevTools 支持]
- immer: [是否需要不可变更新辅助]
## 选择器
提供细粒度的选择器函数,避免不必要的重渲染实际示例:生成购物车 Store
import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
image?: string;
}
interface CartState {
items: CartItem[];
isOpen: boolean;
// 派生值(通过选择器实现)
// totalItems, totalPrice
// Actions
addItem: (item: Omit<CartItem, "quantity">) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
toggleCart: () => void;
}
export const useCartStore = create<CartState>()(
devtools(
persist(
immer((set) => ({
items: [],
isOpen: false,
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.id === item.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...item, quantity: 1 });
}
}),
removeItem: (id) =>
set((state) => {
state.items = state.items.filter((i) => i.id !== id);
}),
updateQuantity: (id, quantity) =>
set((state) => {
const item = state.items.find((i) => i.id === id);
if (item) {
if (quantity <= 0) {
state.items = state.items.filter((i) => i.id !== id);
} else {
item.quantity = quantity;
}
}
}),
clearCart: () =>
set((state) => {
state.items = [];
}),
toggleCart: () =>
set((state) => {
state.isOpen = !state.isOpen;
}),
})),
{ name: "cart-storage" }
),
{ name: "CartStore" }
)
);
// 细粒度选择器——避免不必要的重渲染
export const useCartItems = () => useCartStore((s) => s.items);
export const useCartOpen = () => useCartStore((s) => s.isOpen);
export const useCartTotalItems = () =>
useCartStore((s) => s.items.reduce((sum, item) => sum + item.quantity, 0));
export const useCartTotalPrice = () =>
useCartStore((s) =>
s.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
);5.3 Pinia Store 生成(Vue)
Pinia Store 生成 Prompt
生成一个 Pinia store:use[StoreName]Store
## 技术栈
- Pinia v2 + Vue 3 + TypeScript
- 使用 Setup Store 语法(Composition API 风格)
## 状态
- [field]: [type] — [描述]
## Getters(计算属性)
- [getterName]: [描述派生逻辑]
## Actions
- [actionName]: [描述功能,包括异步操作]
## 要求
1. 使用 Setup Store 语法(defineStore + 函数)
2. 状态使用 ref(),getters 使用 computed()
3. 异步 action 包含 loading/error 状态管理
4. 导出细粒度的 storeToRefs 用法示例实际示例:生成用户认证 Store
import { ref, computed } from "vue";
import { defineStore } from "pinia";
interface User {
id: string;
name: string;
email: string;
role: "admin" | "user";
}
export const useAuthStore = defineStore("auth", () => {
// 状态
const user = ref<User | null>(null);
const token = ref<string | null>(null);
const isLoading = ref(false);
const error = ref<string | null>(null);
// Getters
const isAuthenticated = computed(() => !!token.value);
const isAdmin = computed(() => user.value?.role === "admin");
const displayName = computed(() => user.value?.name ?? "访客");
// Actions
async function login(email: string, password: string) {
isLoading.value = true;
error.value = null;
try {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error("登录失败,请检查邮箱和密码");
}
const data = await response.json();
user.value = data.user;
token.value = data.token;
localStorage.setItem("auth-token", data.token);
} catch (err) {
error.value = err instanceof Error ? err.message : "未知错误";
throw err;
} finally {
isLoading.value = false;
}
}
function logout() {
user.value = null;
token.value = null;
localStorage.removeItem("auth-token");
}
async function checkAuth() {
const savedToken = localStorage.getItem("auth-token");
if (!savedToken) return;
token.value = savedToken;
isLoading.value = true;
try {
const response = await fetch("/api/auth/me", {
headers: { Authorization: `Bearer ${savedToken}` },
});
if (response.ok) {
user.value = await response.json();
} else {
logout();
}
} catch {
logout();
} finally {
isLoading.value = false;
}
}
return {
// 状态
user: readonly(user),
token: readonly(token),
isLoading: readonly(isLoading),
error: readonly(error),
// Getters
isAuthenticated,
isAdmin,
displayName,
// Actions
login,
logout,
checkAuth,
};
});5.4 Svelte Store 生成
Svelte 5 模块级状态 Prompt
生成一个 Svelte 5 共享状态模块:[storeName].svelte.ts
## 技术栈
- Svelte 5 Runes + TypeScript
- 使用 .svelte.ts 文件扩展名(启用 Runes)
## 状态
使用 $state 在模块级别声明共享状态
## 派生值
使用 $derived 声明计算值
## 要求
1. 使用 .svelte.ts 扩展名
2. 导出响应式状态和操作函数
3. 状态变更通过函数封装实际示例:Svelte 5 主题 Store
// theme.svelte.ts
type Theme = "light" | "dark" | "system";
function createThemeStore() {
let theme = $state<Theme>("system");
let resolvedTheme = $derived<"light" | "dark">(
theme === "system"
? (typeof window !== "undefined" &&
window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light")
: theme
);
$effect(() => {
if (typeof document === "undefined") return;
document.documentElement.classList.toggle("dark", resolvedTheme === "dark");
});
return {
get theme() { return theme; },
get resolvedTheme() { return resolvedTheme; },
get isDark() { return resolvedTheme === "dark"; },
setTheme(newTheme: Theme) {
theme = newTheme;
if (typeof localStorage !== "undefined") {
localStorage.setItem("theme", newTheme);
}
},
toggle() {
this.setTheme(resolvedTheme === "dark" ? "light" : "dark");
},
init() {
if (typeof localStorage !== "undefined") {
const saved = localStorage.getItem("theme") as Theme | null;
if (saved) theme = saved;
}
},
};
}
export const themeStore = createThemeStore();5.5 状态管理生成的通用 Prompt 模式
无论使用哪个框架的状态管理方案,以下 Prompt 模式都适用:
生成 [框架] 状态管理代码:
## 1. 状态模型
定义清晰的 TypeScript 接口,区分:
- 数据状态(业务数据)
- UI 状态(加载、错误、打开/关闭)
- 派生状态(从数据状态计算得出)
## 2. 操作(Actions)
每个操作描述:
- 触发条件
- 状态变更逻辑
- 异步操作(如有)
- 错误处理
- 乐观更新(如适用)
## 3. 选择器/Getters
定义细粒度的状态访问方式,避免不必要的重渲染/重计算
## 4. 持久化需求
- 哪些状态需要持久化到 localStorage
- 序列化/反序列化策略
- SSR 兼容性
## 5. 开发工具
- DevTools 集成
- 日志中间件
- 时间旅行调试6. 样式生成策略
6.1 样式方案对比与 AI 适配度
| 样式方案 | AI 适配度 | Token 效率 | 适用框架 | 价格 |
|---|---|---|---|---|
| Tailwind CSS | ⭐⭐⭐⭐⭐ | 最高(内联类名) | React/Vue/Svelte | 开源免费 |
| CSS Modules | ⭐⭐⭐⭐ | 中等(需要额外文件) | React/Vue | 开源免费(内置) |
| styled-components | ⭐⭐⭐ | 较低(模板字符串) | React | 开源免费 |
| Emotion | ⭐⭐⭐ | 较低 | React | 开源免费 |
| Vue Scoped CSS | ⭐⭐⭐⭐ | 高(SFC 内置) | Vue | 开源免费(内置) |
| Svelte Scoped CSS | ⭐⭐⭐⭐⭐ | 最高(默认 scoped) | Svelte | 开源免费(内置) |
| Panda CSS | ⭐⭐⭐ | 中等 | React/Vue/Svelte | 开源免费 |
| vanilla-extract | ⭐⭐⭐ | 中等 | React | 开源免费 |
为什么 Tailwind CSS 是 AI 生成的最佳选择:
- Token 效率最高:样式直接写在 HTML/JSX 中,AI 无需生成额外的样式文件
- 训练数据丰富:Tailwind 是 AI 工具(v0.dev、Bolt.new)的默认样式方案
- 确定性强:原子类名有明确的映射关系,AI 不容易出错
- 无命名负担:不需要为 CSS 类起名,减少 AI 的决策负担
- 响应式内置:
sm:md:lg:前缀让响应式样式生成更直观
6.2 Tailwind CSS 样式生成
Tailwind 样式生成 Prompt
使用 Tailwind CSS 4 为以下组件生成样式:
## 设计 Token
- 主色:[色值] → 映射到 Tailwind 的 [类名]
- 圆角:[数值] → rounded-[size]
- 阴影:[描述] → shadow-[size]
- 间距基准:[数值]
## 响应式断点
- 移动端(默认):[描述布局]
- sm(640px+):[描述变化]
- md(768px+):[描述变化]
- lg(1024px+):[描述变化]
## 暗色模式
- 使用 dark: 前缀
- [描述暗色模式下的颜色变化]
## 动画/过渡
- [描述需要的动画效果]
- 使用 transition-[property] duration-[ms]
## 约束
1. 使用 Tailwind 内置类,不写自定义 CSS
2. 颜色使用 CSS 变量(--primary 等)或 Tailwind 色板
3. 使用 cn() 工具函数合并条件类名
4. 避免过长的类名字符串——超过 5 个类时考虑提取Tailwind 条件样式模式
// 使用 cn() 工具函数(来自 shadcn/ui)
import { cn } from "@/lib/utils";
// 变体样式映射
const variants = {
primary: "bg-blue-500 text-white hover:bg-blue-600",
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200",
danger: "bg-red-500 text-white hover:bg-red-600",
} as const;
const sizes = {
sm: "px-3 py-1.5 text-sm",
md: "px-4 py-2 text-base",
lg: "px-6 py-3 text-lg",
} as const;
// 在组件中使用
<button
className={cn(
"rounded-lg font-medium transition-colors focus:outline-none focus:ring-2",
variants[variant],
sizes[size],
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
{children}
</button>6.3 CSS Modules 样式生成
CSS Modules 生成 Prompt
使用 CSS Modules 为以下 React 组件生成样式:
## 文件结构
- [Component].tsx — 组件逻辑
- [Component].module.css — 样式文件
## 样式要求
1. 使用 CSS 变量引用设计 Token
2. 使用 BEM 命名约定(block__element--modifier)
3. 响应式使用 @media 查询
4. 动画使用 @keyframes
5. 暗色模式使用 [data-theme="dark"] 选择器或 @media (prefers-color-scheme: dark)
## 约束
- 不使用全局样式(:global)除非必要
- 使用 composes 复用样式
- 避免深层嵌套(最多 3 层)CSS Modules 示例
/* Card.module.css */
.card {
border-radius: var(--radius-lg, 12px);
border: 1px solid var(--color-border, #e5e7eb);
background: var(--color-surface, #ffffff);
padding: var(--spacing-4, 16px);
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
}
.card__header {
display: flex;
align-items: center;
gap: var(--spacing-3, 12px);
margin-bottom: var(--spacing-3, 12px);
}
.card__title {
font-size: var(--font-size-lg, 1.125rem);
font-weight: 600;
color: var(--color-text-primary, #111827);
}
.card__body {
color: var(--color-text-secondary, #6b7280);
font-size: var(--font-size-sm, 0.875rem);
line-height: 1.5;
}
.card--elevated {
composes: card;
box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
}
/* 响应式 */
@media (max-width: 768px) {
.card {
padding: var(--spacing-3, 12px);
}
}
/* 暗色模式 */
@media (prefers-color-scheme: dark) {
.card {
background: var(--color-surface-dark, #1f2937);
border-color: var(--color-border-dark, #374151);
}
}// Card.tsx
import styles from "./Card.module.css";
import { cn } from "@/lib/utils";
interface CardProps {
title: string;
children: React.ReactNode;
elevated?: boolean;
className?: string;
}
export function Card({ title, children, elevated, className }: CardProps) {
return (
<div
className={cn(
elevated ? styles["card--elevated"] : styles.card,
className
)}
>
<div className={styles.card__header}>
<h3 className={styles.card__title}>{title}</h3>
</div>
<div className={styles.card__body}>{children}</div>
</div>
);
}6.4 styled-components 样式生成
styled-components 生成 Prompt
使用 styled-components v6 为以下 React 组件生成样式:
## 要求
1. 使用 TypeScript 泛型定义 styled props
2. 使用 css helper 提取可复用样式片段
3. 使用 ThemeProvider 的主题变量
4. 支持 props 驱动的动态样式
5. 使用 attrs 设置默认 HTML 属性
## 主题接口
interface Theme {
colors: { primary: string; secondary: string; ... };
spacing: { sm: string; md: string; lg: string; };
radii: { sm: string; md: string; lg: string; };
}styled-components 示例
import styled, { css } from "styled-components";
interface ButtonStyledProps {
$variant: "primary" | "secondary" | "ghost";
$size: "sm" | "md" | "lg";
$fullWidth?: boolean;
}
const sizeStyles = {
sm: css`
padding: 6px 12px;
font-size: 0.875rem;
`,
md: css`
padding: 8px 16px;
font-size: 1rem;
`,
lg: css`
padding: 12px 24px;
font-size: 1.125rem;
`,
};
const variantStyles = {
primary: css`
background: ${({ theme }) => theme.colors.primary};
color: white;
&:hover { opacity: 0.9; }
`,
secondary: css`
background: ${({ theme }) => theme.colors.secondary};
color: ${({ theme }) => theme.colors.text};
&:hover { opacity: 0.8; }
`,
ghost: css`
background: transparent;
color: ${({ theme }) => theme.colors.text};
&:hover { background: ${({ theme }) => theme.colors.hover}; }
`,
};
export const StyledButton = styled.button<ButtonStyledProps>`
border: none;
border-radius: ${({ theme }) => theme.radii.md};
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
width: ${({ $fullWidth }) => ($fullWidth ? "100%" : "auto")};
${({ $size }) => sizeStyles[$size]}
${({ $variant }) => variantStyles[$variant]}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&:focus-visible {
outline: 2px solid ${({ theme }) => theme.colors.primary};
outline-offset: 2px;
}
`;6.5 样式方案选择决策树
你的项目情况是什么?
│
├─ 新项目,使用 AI 工具生成 → Tailwind CSS
│ 原因:AI 适配度最高,Token 效率最好
│
├─ 已有项目,使用 CSS Modules → 继续使用 CSS Modules
│ 在 Steering 规则中指定 CSS Modules 约定
│
├─ 已有项目,使用 styled-components → 继续使用
│ 在 Steering 规则中指定主题接口和样式模式
│
├─ Vue 项目 → <style scoped> + Tailwind CSS
│ 两者可以共存,Tailwind 用于布局,scoped CSS 用于复杂样式
│
├─ Svelte 项目 → <style> (默认 scoped) + Tailwind CSS
│ Svelte 样式默认组件作用域,与 Tailwind 配合良好
│
└─ 需要运行时主题切换 → CSS 变量 + Tailwind
使用 CSS 变量定义主题 Token,Tailwind 引用变量7. Prompt 模式库
7.1 按组件类型分类的 Prompt 模式
以下是针对不同组件类型的结构化 Prompt 模板,适用于所有框架。
模式 1:表单组件
生成一个 [框架] 表单组件:[FormName]
## 字段定义
| 字段名 | 类型 | 标签 | 验证规则 | 占位文本 |
|--------|------|------|---------|---------|
| [name] | text | [标签] | required, minLength(2) | [占位符] |
| [email] | email | [标签] | required, email | [占位符] |
| [role] | select | [标签] | required | — |
## 验证方案
- React: React Hook Form + Zod
- Vue: VeeValidate + Zod 或 Valibot
- Svelte: Superforms + Zod
## 提交行为
- 提交时显示 loading 状态
- 成功后 [描述成功行为]
- 失败后显示错误消息
## 无障碍
- 每个输入有关联的 label
- 错误消息使用 aria-describedby 关联
- 必填字段标记 aria-required
- 表单使用 role="form" + aria-label模式 2:数据表格组件
生成一个 [框架] 数据表格组件:[TableName]
## 列定义
| 列名 | 字段 | 类型 | 可排序 | 可筛选 | 宽度 |
|------|------|------|--------|--------|------|
| [列名] | [field] | text | ✓ | ✓ | auto |
| [列名] | [field] | number | ✓ | — | 100px |
| [列名] | [field] | date | ✓ | ✓ | 150px |
| 操作 | — | actions | — | — | 120px |
## 功能
- 排序:点击列头切换升序/降序
- 筛选:[描述筛选方式]
- 分页:每页 [N] 条,显示页码导航
- 选择:[单选/多选/无]
- 行操作:[编辑/删除/查看详情]
## 数据源
- 数据通过 props 传入 / API 获取
- 加载状态显示骨架屏
- 空状态显示 [描述空状态 UI]
## 响应式
- 桌面端:完整表格
- 移动端:[卡片视图/水平滚动/折叠行]模式 3:模态框/对话框组件
生成一个 [框架] 模态框组件:[ModalName]
## Props
- open: boolean — 控制显示/隐藏
- onClose: () => void — 关闭回调
- title: string — 标题
- size: "sm" | "md" | "lg" — 尺寸
## 功能
- 点击遮罩层关闭
- 按 Escape 键关闭
- 打开时锁定背景滚动
- 打开时焦点陷阱(focus trap)
- 关闭时恢复焦点到触发元素
## 动画
- 遮罩层:淡入淡出
- 内容区:从底部滑入 + 淡入
## 无障碍
- role="dialog" + aria-modal="true"
- aria-labelledby 指向标题
- 焦点陷阱:Tab 键在模态框内循环
- 关闭按钮有 aria-label模式 4:导航组件
生成一个 [框架] 导航栏组件:[NavName]
## 结构
- Logo(左侧)
- 导航链接(中间):[列出链接项]
- 操作区(右侧):[搜索/通知/用户菜单]
## 响应式行为
- 桌面端:水平导航栏
- 移动端:汉堡菜单 + 侧边抽屉
## 交互
- 当前页面链接高亮
- 下拉菜单(如有子菜单)
- 滚动时 [固定/隐藏/缩小]
## 无障碍
- 使用 <nav> + aria-label
- 移动端菜单按钮有 aria-expanded
- 键盘可导航(Tab + Enter + Escape)模式 5:列表/卡片网格组件
生成一个 [框架] 卡片网格组件:[GridName]
## 数据
- 数据类型:[描述数据接口]
- 数据来源:[props/API/store]
## 布局
- 网格列数:[响应式列数,如 1/2/3/4]
- 间距:[描述间距]
- 卡片内容:[描述卡片结构]
## 功能
- 加载状态:骨架屏卡片
- 空状态:[描述空状态 UI]
- 无限滚动 / 分页 / 加载更多按钮
- 动画:卡片入场动画(stagger)
## 响应式
- 移动端:单列
- 平板:双列
- 桌面:[3-4 列]7.2 按场景分类的高级 Prompt 模式
模式 6:复合组件模式(Compound Components)
生成一个 [框架] 复合组件:[ComponentName]
## 组件结构
使用复合组件模式(Compound Components),通过子组件组合实现灵活的 API:
<[ComponentName]>
<[ComponentName].Header>标题</[ComponentName].Header>
<[ComponentName].Body>内容</[ComponentName].Body>
<[ComponentName].Footer>底部</[ComponentName].Footer>
</[ComponentName]>
## 实现方式
- React: 使用 React.createContext + 子组件挂载到父组件
- Vue: 使用 provide/inject + 具名插槽
- Svelte: 使用 Context API + 插槽
## 共享状态
父组件通过 Context 向子组件传递:
- [共享状态 1]
- [共享状态 2]
## 要求
1. 子组件只能在父组件内使用
2. 子组件顺序可自由调整
3. 支持条件渲染子组件模式 7:无限滚动列表
生成一个 [框架] 无限滚动列表组件:
## 数据获取
- API 端点:[描述分页 API]
- 每页数量:[N]
- 使用 [TanStack Query / 自定义 Hook / Composable]
## 虚拟化
- 列表项高度:[固定/动态]
- 使用 [react-window / @tanstack/virtual / 自定义]
- 可视区域外的 DOM 节点回收
## 加载指示
- 底部加载指示器(IntersectionObserver 触发)
- 首次加载骨架屏
- 加载失败重试按钮
## 性能要求
- 10000+ 条数据流畅滚动
- 内存占用稳定(不随数据量线性增长)模式 8:实时搜索组件
生成一个 [框架] 实时搜索组件:
## 功能
- 输入防抖(300ms)
- 搜索结果下拉列表
- 键盘导航(上下箭头选择,Enter 确认,Escape 关闭)
- 搜索历史(最近 5 条)
- 高亮匹配文本
## 数据源
- API 搜索:[描述 API]
- 本地过滤:[描述本地数据]
## 无障碍
- role="combobox" + aria-expanded
- 结果列表 role="listbox"
- 选项 role="option" + aria-selected
- 实时结果数量通过 aria-live 播报模式 9:多步骤表单/向导
生成一个 [框架] 多步骤表单组件:
## 步骤定义
| 步骤 | 标题 | 字段 | 验证 |
|------|------|------|------|
| 1 | [标题] | [字段列表] | [验证规则] |
| 2 | [标题] | [字段列表] | [验证规则] |
| 3 | [标题] | [字段列表] | [验证规则] |
## 导航
- 步骤指示器(显示当前步骤和总步骤)
- 上一步/下一步按钮
- 每步验证通过后才能进入下一步
- 支持直接点击步骤指示器跳转(仅已完成的步骤)
## 状态管理
- 跨步骤的表单数据持久化
- 浏览器刷新后恢复进度(可选)
- 最终提交时合并所有步骤数据
## 动画
- 步骤切换滑动动画(左右方向)模式 10:仪表板布局组件
生成一个 [框架] 仪表板布局组件:
## 布局结构
- 顶部导航栏(固定)
- 左侧边栏(可折叠)
- 主内容区(自适应)
- 右侧面板(可选,可关闭)
## 侧边栏
- 导航菜单项:[列出菜单项]
- 支持多级菜单(折叠/展开)
- 折叠模式:仅显示图标
- 当前页面高亮
## 响应式
- 桌面端:侧边栏常驻
- 平板:侧边栏可折叠
- 移动端:侧边栏变为抽屉(overlay)
## 状态
- 侧边栏折叠状态持久化到 localStorage
- 当前路由与菜单项同步7.3 组件质量验证 Prompt
生成组件后,使用以下 Prompt 进行质量验证:
审查以下 [框架] 组件的质量,检查以下维度:
## 1. TypeScript 类型安全
- Props 接口是否完整且准确
- 是否有 any 类型
- 事件处理器类型是否正确
- 泛型使用是否合理
## 2. 无障碍合规
- 语义化 HTML 标签使用
- ARIA 属性完整性
- 键盘导航支持
- 颜色对比度
- 屏幕阅读器兼容性
## 3. 性能
- 是否有不必要的重渲染
- 是否需要 memo/useMemo/useCallback
- 事件处理器是否在渲染中创建新引用
- 大列表是否需要虚拟化
## 4. 错误处理
- 边界情况处理(空数据、null、undefined)
- 加载状态和错误状态
- 错误边界(Error Boundary)
## 5. 响应式
- 所有断点是否测试
- 触摸设备兼容性
- 文本溢出处理
## 6. 测试覆盖
- 是否包含单元测试
- 关键交互是否有测试
- 边界情况是否有测试
请逐项检查并给出改进建议。8. 组件测试生成策略
8.1 组件测试生成 Prompt
为以下 [框架] 组件生成测试:
## 测试框架
- React: Vitest + @testing-library/react
- Vue: Vitest + @vue/test-utils + @testing-library/vue
- Svelte: Vitest + @testing-library/svelte
## 测试类型
1. **渲染测试**:组件是否正确渲染
2. **Props 测试**:不同 props 组合的渲染结果
3. **交互测试**:用户操作后的状态变化
4. **无障碍测试**:ARIA 属性和键盘导航
5. **快照测试**:UI 结构稳定性(可选)
## 测试用例
| 用例 | 描述 | 预期结果 |
|------|------|---------|
| [用例名] | [操作描述] | [预期结果] |
## 约束
1. 使用 @testing-library 的用户行为模拟(userEvent)
2. 查询优先级:getByRole > getByLabelText > getByText > getByTestId
3. 不测试实现细节,测试用户可见的行为
4. 异步操作使用 waitFor / findBy8.2 React 组件测试示例
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import { UserCard } from "./UserCard";
const mockUser = {
id: "1",
name: "张三",
email: "zhangsan@example.com",
role: "admin" as const,
isOnline: true,
};
describe("UserCard", () => {
it("渲染用户基本信息", () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText("张三")).toBeInTheDocument();
expect(screen.getByText("zhangsan@example.com")).toBeInTheDocument();
expect(screen.getByText("管理员")).toBeInTheDocument();
});
it("显示在线状态", () => {
render(<UserCard user={mockUser} />);
expect(screen.getByText("在线")).toBeInTheDocument(); // sr-only 文本
});
it("显示离线状态", () => {
render(<UserCard user={{ ...mockUser, isOnline: false }} />);
expect(screen.getByText("离线")).toBeInTheDocument();
});
it("点击编辑按钮触发回调", async () => {
const onEdit = vi.fn();
render(<UserCard user={mockUser} onEdit={onEdit} />);
await userEvent.click(screen.getByRole("button", { name: /编辑 张三/ }));
expect(onEdit).toHaveBeenCalledWith("1");
});
it("不传 onEdit 时不显示编辑按钮", () => {
render(<UserCard user={mockUser} />);
expect(screen.queryByRole("button", { name: /编辑/ })).not.toBeInTheDocument();
});
it("无头像时显示姓名首字母", () => {
render(<UserCard user={{ ...mockUser, avatar: undefined }} />);
expect(screen.getByText("张")).toBeInTheDocument();
});
});实战案例:用 AI 生成完整的电商产品列表模块
案例背景
一个电商项目需要构建产品列表模块,包含:
- 产品卡片组件
- 筛选侧边栏
- 排序下拉菜单
- 分页组件
- 购物车集成
技术栈:React + Next.js 15 + TypeScript + Tailwind CSS + Zustand
步骤 1:定义数据模型
首先定义产品列表模块的 TypeScript 类型:
interface Product {
id: string;
name: string;
description: string;
price: number;
originalPrice?: number;
image: string;
category: string;
tags: string[];
rating: number;
reviewCount: number;
inStock: boolean;
}
interface ProductFilters {
category: string | null;
priceRange: [number, number] | null;
inStockOnly: boolean;
tags: string[];
sortBy: "price-asc" | "price-desc" | "rating" | "newest";
}
interface ProductListState {
products: Product[];
filters: ProductFilters;
currentPage: number;
totalPages: number;
isLoading: boolean;
}步骤 2:生成 Zustand Store
使用 5.2 节的 Zustand Store 生成 Prompt,创建产品列表的状态管理。
步骤 3:逐组件生成
按照从底层到顶层的顺序生成组件:
1. ProductCard — 单个产品卡片(第一层:UI 结构)
2. ProductGrid — 产品网格布局(第一层:布局)
3. FilterSidebar — 筛选侧边栏(第二层:交互逻辑)
4. SortDropdown — 排序下拉菜单(第二层:交互逻辑)
5. Pagination — 分页组件(第二层:交互逻辑)
6. ProductListPage — 页面组装(第四层:组件组合)步骤 4:为每个组件编写 Prompt
以 ProductCard 为例:
生成一个 React 函数组件 ProductCard:
## 技术栈
- React 19 + TypeScript + Tailwind CSS + shadcn/ui
## Props
interface ProductCardProps {
product: Product;
onAddToCart: (productId: string) => void;
className?: string;
}
## 功能
- 显示产品图片(16:9 比例,hover 时轻微放大)
- 显示产品名称(最多 2 行,溢出省略)
- 显示价格(有原价时显示划线价 + 折扣标签)
- 显示评分(星星图标 + 评价数)
- 显示库存状态(有货/缺货)
- "加入购物车"按钮(缺货时禁用)
## 无障碍
- 卡片使用 article 标签
- 图片有描述性 alt
- 按钮有明确的 aria-label
- 价格变化有 sr-only 说明步骤 5:集成测试
生成所有组件后,编写集成测试验证组件间的交互:
为产品列表模块编写集成测试:
1. 筛选操作后产品列表正确更新
2. 排序切换后产品顺序正确
3. 分页切换后显示正确的产品
4. 加入购物车后购物车数量更新
5. 加载状态正确显示骨架屏
6. 空结果显示空状态 UI案例总结
| 阶段 | 耗时 | 组件数 |
|---|---|---|
| 数据模型定义 | 5 分钟 | — |
| Zustand Store 生成 | 10 分钟 | 1 |
| 组件逐个生成 | 40 分钟 | 6 |
| 人工审查与调整 | 20 分钟 | — |
| 测试生成与验证 | 15 分钟 | — |
| 总计 | 约 90 分钟 | 7 |
传统手工开发同等模块通常需要 1-2 天。关键在于:逐组件生成、每个组件独立验证、最后组装集成。
避坑指南
❌ 常见错误
-
一次性生成整个页面而非逐组件
- 问题:AI 生成的大型组件往往包含紧耦合的逻辑、重复代码和难以维护的结构
- 正确做法:将页面拆分为 5-10 个独立组件,逐个生成,每个组件独立验证后再组装
-
不指定框架版本和语法偏好
- 问题:AI 可能生成 Vue 2 Options API、Svelte 4
$:语法、React Class 组件等过时代码 - 正确做法:在 Prompt 中明确指定”Vue 3 +
<script setup>”、“Svelte 5 Runes”、“React 函数组件”
- 问题:AI 可能生成 Vue 2 Options API、Svelte 4
-
忽略 Hooks/Composables 的规则
- 问题:AI 生成的 React Hooks 可能在条件语句中调用、缺少依赖数组项、产生闭包陷阱
- 正确做法:在 Prompt 中明确要求”遵循 Hooks 规则”,并使用 ESLint
react-hooks/exhaustive-deps规则验证
-
状态管理过度设计
- 问题:AI 倾向于为简单场景引入 Redux 或复杂的 Context 层级
- 正确做法:在 Prompt 中明确状态范围——“局部状态用 useState,跨组件状态用 Zustand,服务端状态用 TanStack Query”
-
样式方案混用
- 问题:AI 在同一项目中混用 Tailwind、inline style、CSS Modules,导致样式混乱
- 正确做法:在 Steering 规则中统一样式方案,Prompt 中明确指定”仅使用 Tailwind CSS,不使用 inline style”
-
忽略 TypeScript 严格模式
- 问题:AI 生成的代码包含
any类型、缺少 null 检查、类型断言过多 - 正确做法:在 Prompt 中要求”TypeScript strict mode,不使用 any,使用类型守卫而非类型断言”
- 问题:AI 生成的代码包含
-
不验证无障碍合规
- 问题:AI 生成的组件缺少 ARIA 属性、键盘导航、焦点管理
- 正确做法:在每个组件 Prompt 中包含无障碍要求,生成后运行 axe-core 检查
-
复制 AI 生成的测试而不理解
- 问题:AI 生成的测试可能测试实现细节而非用户行为,或使用错误的查询方式
- 正确做法:审查测试逻辑,确保使用 @testing-library 的最佳实践(按角色查询、模拟用户行为)
✅ 最佳实践
- 建立组件生成 Checklist:每个组件生成后检查——TypeScript 类型完整、无障碍属性齐全、响应式布局正确、测试覆盖关键路径
- 使用 Steering 规则统一技术栈:在项目开始前定义框架版本、样式方案、状态管理、测试框架,避免 AI 每次生成时做不同选择
- 从 Props 接口开始设计:先定义组件的 TypeScript Props 接口,再让 AI 实现——接口即契约,约束 AI 的生成范围
- 逐层验证:先验证 UI 结构和样式(第一层),再添加交互逻辑(第二层),最后集成状态管理(第三层)
- 保持组件单一职责:每个组件只做一件事,复杂 UI 通过组合多个简单组件实现
- 为每个框架维护 Prompt 模板库:将验证有效的 Prompt 模式保存为模板,团队共享复用
- 定期更新 Steering 规则:随着框架版本更新(如 Svelte 5 Runes、Vue 3.5 新特性),及时更新 Steering 规则中的语法偏好
相关资源与延伸阅读
官方文档
- React 官方文档 — 组件与 Hooks — React 19 函数组件和 Hooks 的权威参考
- Vue 3 官方文档 — Composition API — Vue 3 Composition API 和
<script setup>的完整指南 - Svelte 5 官方文档 — Runes — Svelte 5 Runes 响应式系统的官方文档
- Tailwind CSS 文档 — Tailwind CSS 4 的完整类名参考和配置指南
- shadcn/ui 组件库 — React + Tailwind 的高质量组件原语,AI 工具的默认组件库
状态管理
- Zustand 官方文档 — React 轻量状态管理库,AI 生成友好的简洁 API
- Pinia 官方文档 — Vue 官方状态管理库,支持 Composition API 风格的 Setup Store
工具与平台
- v0.dev — Vercel 的 AI UI 生成器,React + Tailwind + shadcn/ui 组件生成的最佳工具
- Testing Library 文档 — 前端组件测试的最佳实践,支持 React/Vue/Svelte
- TanStack Query 文档 — 服务端状态管理库,支持 React/Vue/Svelte/Angular
参考来源
- The React + AI Stack for 2026 (2026-01)
- Why AI Vibe-Coding Tools Default to React, Next.js, and Tailwind (2025-12)
- Why AI Writes Better Svelte 6 Code Than React Code (2026-02)
- Reactive Magic in Svelte 5: Understanding Runes (2026-01)
- JavaScript Framework Trends in 2026 (2026-01)
- State Management in 2026: Redux, Context API, and Modern Patterns (2026-01)
- A Dev’s Guide to Tailwind CSS in 2026 (2025-12)
- AI Prompt Engineering for Web Development (2026-01)
- From Prompt to Production-Ready SaaS — v0.dev Guide 2026 (2026-01)
- Fastest Frontend Tooling for Humans & AI (2026-02)
- AI UI Component Generation Guide — Mastering v0 (2026-01)
- CSS Modules vs Tailwind CSS (2026-01)
- Leveraging Pinia to Simplify Complex Vue State Management (2025-06)
- Svelte 5 Runes: A Complete Guide for Vue Developers (2025-12)
📖 返回 总览与导航 | 上一节:27a-AI辅助前端开发概览 | 下一节:27c-设计到代码工作流