0Pricing
React Academy · 课时

泛型组件与实用工具类型

编写可复用的泛型组件,并使用 PropsWithChildren、ComponentProps 和 Omit。

泛型组件与实用工具类型 是 CoddyKit 上的免费 React Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。

为什么需要泛型组件

泛型组件可以适配不同的数据类型,同时保持完整的类型安全性,例如适用于任意数据结构的类型化列表、选择框或表格。

简单的泛型列表

在函数上使用类型参数 T,即可创建能够渲染任意项目类型的列表组件。

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, i) => (
        <li key={keyExtractor(item)}>{renderItem(item, i)}</li>
      ))}
    </ul>
  );
}

约束泛型类型

使用 extends 添加约束,让 TypeScript 知道该类型必须包含哪些属性。

interface SelectProps<T extends { id: string; label: string }> {
  options: T[];
  value: T | null;
  onChange: (option: T) => void;
}

function Select<T extends { id: string; label: string }>(
  { options, value, onChange }: SelectProps<T>
) {
  return (
    <select value={value?.id} onChange={e => onChange(options.find(o => o.id === e.target.value)!)}>
      {options.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
    </select>
  );
}

PropsWithChildren

React.PropsWithChildren<P> 会将 children?: React.ReactNode 添加到任意属性类型中,适合包装器组件使用。

function Card({ title, children }: React.PropsWithChildren<{ title: string }>) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

ComponentProps

React.ComponentProps<typeof Component> 可以提取现有组件的属性,适合需要扩展或包装组件的场景。

import { Button } from './Button';

type ExtendedButtonProps = React.ComponentProps<typeof Button> & {
  loading?: boolean;
};

function LoadingButton({ loading, children, ...props }: ExtendedButtonProps) {
  return (
    <Button {...props} disabled={loading || props.disabled}>
      {loading ? <Spinner /> : children}
    </Button>
  );
}

ComponentPropsWithoutRef

React.ComponentPropsWithoutRef<'input'> 会提取 HTML 元素的属性,但不包含 ref 属性,非常适合包装 HTML 元素。

type InputProps = React.ComponentPropsWithoutRef<'input'> & {
  label: string;
  error?: string;
};

function FormInput({ label, error, ...inputProps }: InputProps) {
  return (
    <div>
      <label>{label}</label>
      <input {...inputProps} />
      {error && <span className="error">{error}</span>}
    </div>
  );
}

Omit 与 Pick

使用 Omit<T, Keys> 移除特定属性,使用 Pick<T, Keys> 仅保留现有类型中的特定属性。

interface UserCardProps {
  id: string;
  name: string;
  email: string;
  avatar: string;
  role: string;
}

// Only expose non-sensitive fields:
type PublicUserCardProps = Omit<UserCardProps, 'email' | 'role'>;
// Or pick exactly what you need:
type AvatarProps = Pick<UserCardProps, 'name' | 'avatar'>;

Partial 与 Required

Partial<T> 会将所有属性变为可选属性(适合更新表单)。Required<T> 会将所有属性变为必需属性。

interface UserFormData { name: string; email: string; bio: string; }

// For update: only send changed fields
type UserUpdatePayload = Partial<UserFormData>;

// For create: all fields are mandatory
type UserCreatePayload = Required<UserFormData>;

使用 Record 创建类型化字典

Record<Keys, Value> 会创建一个具有特定键类型的对象类型,适合查找映射和翻译。

type Status = 'idle' | 'loading' | 'success' | 'error';

const statusLabels: Record<Status, string> = {
  idle: 'Ready',
  loading: 'Loading...',
  success: 'Done!',
  error: 'Something went wrong',
};

使用 ReturnType 获取钩子类型

使用 ReturnType<typeof useMyHook> 推断自定义钩子的返回类型,无需手动重复定义该类型。

function useCounter(initial: number) {
  const [count, setCount] = useState(initial);
  return { count, increment: () => setCount(c => c + 1) };
}

type CounterHook = ReturnType<typeof useCounter>;
// { count: number; increment: () => void }

泛型表格组件

这是一个完全类型安全的数据表格,适用于任意行结构,可以推断列键,同时仍提供自动补全。

interface Column<T> {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T], row: T) => React.ReactNode;
}

function DataTable<T extends object>({ data, columns }: { data: T[]; columns: Column<T>[] }) {
  return (
    <table>
      <thead><tr>{columns.map(c => <th key={String(c.key)}>{c.header}</th>)}</tr></thead>
      <tbody>{data.map((row, i) => (
        <tr key={i}>{columns.map(c => <td key={String(c.key)}>{c.render ? c.render(row[c.key], row) : String(row[c.key])}</td>)}</tr>
      ))}</tbody>
    </table>
  );
}

快速检查

哪种工具类型可以从现有的 TypeScript 接口中移除指定的键?

回顾

泛型组件使用类型参数,在不同数据结构之间保持类型安全。Omit、Pick、Partial、ComponentProps 和 PropsWithChildren 等工具类型可以消除手动重复定义类型的需要,并使组件接口更易于组合。

常见问题解答

「泛型组件与实用工具类型」课时是免费的吗?

是的 — 「泛型组件与实用工具类型」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。

「泛型组件与实用工具类型」这节课中我会学到什么?

编写可复用的泛型组件,并使用 PropsWithChildren、ComponentProps 和 Omit。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「泛型组件与实用工具类型」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Academy 课中编写并运行代码吗?

能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 为属性与组件返回值定义类型
  2. 在 TypeScript 中为事件与引用定义类型
  3. 泛型组件与实用工具类型
  4. 为 Context 与自定义钩子定义类型
← 返回 React Academy