React组件开发技能Skill react-component

React组件开发技能是专门用于创建React UI组件的专家指导体系,涵盖函数组件、钩子、自定义钩子和组件组合模式。该技能专注于TypeScript类型安全、性能优化、可访问性设计和现代React最佳实践,适用于构建ERP系统、仪表板、KPI卡片、表单、模态框等企业级UI组件。关键词:React组件开发、TypeScript、函数组件、自定义钩子、组件组合、性能优化、可访问性、UI设计、前端开发、React最佳实践

前端开发 0 次安装 0 次浏览 更新于 3/2/2026

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类型
  • 保持组件小巧且专注(单一职责)

输出要求

代码文件

  1. 组件文件 (ComponentName.tsx):

    • 带有类型化props的函数组件
    • 正确应用的钩子
    • 作为命名导出导出
    • 适当时使用默认导出
  2. 测试文件 (ComponentName.test.tsx):

    • Jest/Vitest + React Testing Library
    • 测试组件使用props渲染
    • 测试用户交互
    • 测试加载/错误状态
    • 可访问性测试
  3. Storybook文件 (ComponentName.stories.tsx):

    • 默认故事
    • 变体故事(加载、错误、不同状态)
    • 用于文档的props表格

集成要求

  • shadcn/ui:可用时使用现有的shadcn组件
  • 可访问性:遵循WCAG 2.1 AA指南(使用@ui-ux-designer进行可访问性审计)
  • 样式:使用设计系统令牌的Tailwind CSS
  • i18n:为国际化准备组件(无硬编码文本)

文档

  • PHR:为每个组件开发创建提示历史记录
  • ADR:为复杂自定义钩子记录钩子模式决策
  • 注释:仅对非明显逻辑添加注释

工作流程

  1. 理解需求

    • 明确组件目的、props和交互
    • 识别状态需求(本地vs全局)
    • 确定可重用性潜力
  2. 设计Props接口

    • 为所有props定义TypeScript接口
    • 使用?标记可选props
    • 对变体类型使用判别联合类型
  3. 实现组件

    • 编写函数组件
    • 应用带有适当依赖项的钩子
    • 处理加载/错误状态
    • 确保可访问性(ARIA属性)
  4. 测试组件

    • 为所有代码路径编写单元测试
    • 测试用户交互
    • 验证可访问性
  5. 创建故事

    • 使用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>
  );
};

参考