name: react-component description: | 在创建React UI组件时使用 - 函数组件、钩子、自定义钩子或组件组合模式。 不涉及后端逻辑、API路由或非React框架。 触发词:“创建组件”、“构建小部件”、“KPI卡片”、“表单”、“模态框”、“自定义钩子”、“useContext”、“useState”、“useEffect”。
React组件技能
概述
构建React函数组件、钩子和组合模式的专家指导。专注于TypeScript、性能、可访问性和现代React最佳实践。
适用场景
当用户请求以下内容时触发此技能:
- UI组件:“创建学生KPI卡片”、“构建模态框”、“表单组件”
- 钩子:“自定义考勤钩子”、“主题useContext”、“数据useState”
- 模式:“组件组合”、“HOC”、“render props”
- ERP小部件:KPI卡片、表单、数据表、仪表板
核心规则
1. 始终使用函数组件
// ✅ 正确:使用TypeScript的函数组件
interface StudentKPICardProps {
studentName: string;
attendance: number;
loading?: boolean;
}
export const StudentKPICard = React.memo(({ studentName, attendance, loading = false }: StudentKPICardProps) => {
const [isHovered, setIsHovered] = useState(false);
if (loading) return <LoadingSkeleton />;
return <div>{/* 内容 */}</div>;
});
要求:
- 始终使用函数组件(不使用类组件)
- 明确类型化所有props接口
- 对频繁接收相同props的组件使用
React.memo - 需要ref转发时使用
React.forwardRef
2. 钩子使用
// ✅ 正确:带有清理功能的正确钩子使用
export const AttendanceMonitor = ({ studentId }: { studentId: string }) => {
const [data, setData] = useState(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let isMounted = true;
const fetchAttendance = async () => {
try {
const result = await api.getAttendance(studentId);
if (isMounted) setData(result);
} catch (err) {
if (isMounted) setError(err.message);
}
};
fetchAttendance();
return () => {
isMounted = false;
};
}, [studentId]);
return /* JSX */;
};
要求:
useState:仅用于本地组件状态useEffect:用于带有适当清理函数的副作用useContext:用于全局状态(主题、认证、语言)- 始终在依赖数组中包含所有依赖项
- 在列表中对昂贵操作使用
useCallback/useMemo
3. 自定义钩子
// ✅ 正确:带有适当类型的自定义钩子
interface UseAttendanceResult {
data: AttendanceData | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
export const useAttendance = (studentId: string): UseAttendanceResult => {
const [data, setData] = useState<AttendanceData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchAttendance = useCallback(async () => {
setLoading(true);
try {
const result = await api.getAttendance(studentId);
setData(result);
setError(null);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [studentId]);
useEffect(() => {
fetchAttendance();
}, [fetchAttendance]);
return { data, loading, error, refetch: fetchAttendance };
};
要求:
- 所有自定义钩子必须以
use前缀开头 - 将可重用逻辑提取到钩子中
- 为钩子结果返回类型化接口
- 包含加载、错误状态和重试机制
- 保持钩子专注于单一职责
4. 组件组合
// ✅ 正确:通过children属性进行组合
interface CardProps {
title: string;
children: React.ReactNode;
footer?: React.ReactNode;
}
export const Card = ({ title, children, footer }: CardProps) => (
<div className="card">
<h2>{title}</h2>
<div className="card-content">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
// 使用方式
<Card title="学生信息" footer={<Button>保存</Button>}>
<StudentDetails />
</Card>
// ✅ 正确:高阶组件模式
export const withLoading = <P extends object>(WrappedComponent: React.ComponentType<P>) => {
return (props: P & { loading?: boolean }) => {
const { loading = false, ...rest } = props;
if (loading) return <LoadingSpinner />;
return <WrappedComponent {...(rest as P)} />;
};
};
要求:
- 优先使用组合而非继承
- 使用
children属性实现灵活内容 - 当组件需要共享数据时使用render props
- 谨慎使用HOC并配合适当的TypeScript类型
- 保持组件小巧且专注(单一职责)
输出要求
代码文件
-
组件文件 (
ComponentName.tsx):- 带有类型化props的函数组件
- 正确应用的钩子
- 作为命名导出导出
- 适当时使用默认导出
-
测试文件 (
ComponentName.test.tsx):- Jest/Vitest + React Testing Library
- 测试组件使用props渲染
- 测试用户交互
- 测试加载/错误状态
- 可访问性测试
-
Storybook文件 (
ComponentName.stories.tsx):- 默认故事
- 变体故事(加载、错误、不同状态)
- 用于文档的props表格
集成要求
- shadcn/ui:可用时使用现有的shadcn组件
- 可访问性:遵循WCAG 2.1 AA指南(使用@ui-ux-designer进行可访问性审计)
- 样式:使用设计系统令牌的Tailwind CSS
- i18n:为国际化准备组件(无硬编码文本)
文档
- PHR:为每个组件开发创建提示历史记录
- ADR:为复杂自定义钩子记录钩子模式决策
- 注释:仅对非明显逻辑添加注释
工作流程
-
理解需求
- 明确组件目的、props和交互
- 识别状态需求(本地vs全局)
- 确定可重用性潜力
-
设计Props接口
- 为所有props定义TypeScript接口
- 使用
?标记可选props - 对变体类型使用判别联合类型
-
实现组件
- 编写函数组件
- 应用带有适当依赖项的钩子
- 处理加载/错误状态
- 确保可访问性(ARIA属性)
-
测试组件
- 为所有代码路径编写单元测试
- 测试用户交互
- 验证可访问性
-
创建故事
- 使用Storybook记录组件
- 显示所有变体和状态
- 添加控件用于交互式探索
质量检查清单
完成任何组件前:
- [ ] React 19+钩子规则:无依赖问题,useEffect中适当的清理
- [ ] TypeScript Props:详尽的类型定义,无
any类型 - [ ] 自定义钩子:所有以’use’为前缀,单一职责
- [ ] 组合:使用组合而非继承,通过children/render props实现灵活性
- [ ] 性能:在列表中对昂贵操作使用
useCallback/useMemo - [ ] 可访问性:适当的ARIA标签,键盘导航支持
- [ ] 错误处理:优雅的错误状态,无控制台错误
- [ ] 加载状态:异步操作的清晰加载指示器
- [ ] 测试:单元测试覆盖所有分支和交互
- [ ] 故事:Storybook故事记录组件使用方式
常见模式
数据获取组件
export const StudentList = () => {
const { data: students, loading, error } = useStudents();
if (loading) return <LoadingSkeleton count={5} />;
if (error) return <ErrorState message={error} />;
return (
<ul>
{students?.map((student) => (
<StudentItem key={student.id} student={student} />
))}
</ul>
);
};
带有验证的表单组件
interface FormValues {
name: string;
email: string;
}
export const StudentForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm<FormValues>();
const onSubmit = async (data: FormValues) => {
await api.createStudent(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register('name', { required: true })} error={errors.name} />
<Input {...register('email', { required: true })} error={errors.email} />
<Button type="submit">保存</Button>
</form>
);
};
参考
- React文档:https://react.dev
- TypeScript React速查表:https://react-typescript-cheatsheet.netlify.app
- React Testing Library:https://testing-library.com/docs/react-testing-library/intro
- shadcn/ui:https://ui.shadcn.com