泛型组件与实用工具类型
编写可复用的泛型组件,并使用 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 反馈 — 无需本地设置。