React 与 TypeScript 类型进阶:用泛型组件与可辨识联合把 props 严丝合缝
test12026-10-2916 次阅读
为什么要在组件上做类型收敛
很多团队把组件的 props 写成宽松的 any 或大而全的可选字段,结果运行时才暴露“少传了一个字段”或“传错了类型”的问题。React 配合 TypeScript 可以把这一类错误前移到编译期。本文用两个具体手段:泛型组件和可辨识联合(discriminated union),把 props 的类型边界收紧。
一、泛型组件:让列表组件吃得下任意数据
写一个通用列表组件,数据项类型由调用方决定,组件内部只关心“怎么渲染每一项”。用泛型参数把数据形状和渲染函数绑定起来。
type ListProps<T> = {
items: T[];
keyOf: (item: T) => string | number;
renderItem: (item: T) => React.ReactNode;
};
function GenericList<T>({ items, keyOf, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item) => (
<li key={keyOf(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// 调用时类型被推导出来,user 一定是 { id: number; name: string }
const users = [{ id: 1, name: "Ada" }];
<GenericList items={users} keyOf={(u) => u.id} renderItem={(u) => u.name} />;这里 T 由 users 推导为 { id: number; name: string },如果 renderItem 里写成 u.age 就会在编译期报错,而不是等产品上线后白屏。
二、可辨识联合:让组件的“模式分支”穷尽且类型安全
组件经常需要根据 mode 渲染不同形态。把 mode 和对应字段放进可辨识联合,配合 switch 的穷尽检查,漏掉一种情况编译器会直接提醒你。
type ButtonProps =
| { variant: "solid"; color: string }
| { variant: "ghost"; borderColor: string }
| { variant: "link"; href: string };
function Button(props: ButtonProps) {
switch (props.variant) {
case "solid":
return <button style={{ background: props.color }} />;
case "ghost":
return <button style={{ borderColor: props.borderColor }} />;
case "link":
return <a href={props.href}>链接</a>;
default: {
const _exhaustive: never = props;
return _exhaustive;
}
}
}switch 的 default 分支把 props 赋给 never,一旦以后新增一个 variant 却忘了处理,TypeScript 会在此处报错,等于给未来的自己留了一道护栏。
三、把事件回调的参数也收敛掉
自定义事件不要一律写成 (e: any) => void。给回调一个精确的参数类型,父组件传函数时就能拿到正确的字段。
type UploadProps = {
onProgress: (payload: { loaded: number; total: number }) => void;
};
function Upload({ onProgress }: UploadProps) {
onProgress({ loaded: 40, total: 100 });
}小结
泛型解决“组件吃得下任意但类型自洽的数据”,可辨识联合解决“组件有多种互斥形态时要穷尽处理”。两者结合,组件的 props 接口会从“尽力而为”变成“编译期担保”。
T
test1
文章作者
为什么要在组件上做类型收敛 很多团队把组件的 props 写成宽松的 any 或大而全的可选字段,结果运行时才暴露“少传...
- 分类
- 技术
- 发布时间
- 2026-10-29
- 字数
- 约 1978 字
- 阅读
- 16 次
