类型安全的组件设计
类型安全的组件设计 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能用 TypeScript 的属性联合与泛型,把「组件支持什么、不支持什么」写进类型:非法的参数组合在编译期报错,列表组件的回调参数类型自动跟随数据类型;理解「类型即文档、报错即设计反馈」,同时知道类型管不到的运行时边界。
问题场景
按钮组件第一版用 JS 写:variant 接受任意字符串,手滑拼错的 "bluе"(末尾混进西里尔字母)混进来毫无反应;提示组件 Toast 有四种形态,loading 态不该有关闭按钮,但调用方随手传了也没人拦;列表组件 List 的 onSelect 回调参数永远是 any,字段名全靠猜。这些都不是运行时 bug,而是契约没有写进类型。
核心解释
两类武器:字面量联合表达封闭集合,可辨识联合表达互斥组合。
type ButtonVariant = "primary" | "ghost" | "danger";
// variant="blue" → 编译期直接报错,拼错活不过 tsc
type ToastProps =
| { kind: "info"; text: string }
| { kind: "loading"; text: string } // 没有 closable 字段
| { kind: "error"; text: string; retry: () => void };
// <Toast kind="loading" text="加载中" closable /> → 报错:loading 态不存在 closable
// <Toast kind="error" text="失败" /> → 报错:缺少必填的 retry
可辨识联合的关键是「判别字段」(kind):TypeScript 按判别值收窄类型——组件内部 switch (props.kind) 时每个分支拿到精确的属性集;外部调用时非法组合直接编译失败。「支持的组合」与「能通过编译的调用」从此是同一个集合,这就是类型作为设计反馈的含义。
泛型解决「类型跟随数据」:
type ListProps<T> = {
items: T[];
renderItem: (item: T) => React.ReactNode;
onSelect?: (item: T) => void;
};
function List<T>({ items, renderItem, onSelect }: ListProps<T>) { /* … */ }
// <List items={courses} onSelect={(c) => ...} /> → c 自动是 Course 类型
使用边界也要说清:类型只管编译期,拦不住运行时的脏数据——接口返回的 variant 仍可能非法,入口处仍要校验;类型也别设计得过深,三层以上的条件类型让同事读不懂时,类型本身就成了负债。验证方式:在 TS 文件里故意写出三种非法调用,确认编译器逐个报错,再改对。
示例拆解
正文用 TS 展示契约;实验区用可运行的等价 JS 演示运行时行为:
- Toast 按 kind 渲染四种形态(info / loading / error / success);JS 版在运行时检查「TS 里本该编译期报错」的调用:未知 kind 直接抛错,loading 态传 closable、error 态缺 retry 输出警告;
- 泛型 List 在 JS 里退化为普通组件,配合注释标注类型;点击条目,回调直接拿到课程对象;
- 控制台输出三条「非法用法演示」日志,逐条对应 TS 里会报错的写法;
- 验证:打开控制台读三条日志,把每条对应回正文中会编译失败的那一行代码。
动手任务
打开本课实验区:
- 给 Toast 增加第四种 kind:"success"(带 closable),先在正文风格里写出 TS 类型,再补 JS 渲染分支。
- 把 List 的 onSelect 从「传对象」改成「传索引」,评估两种回调契约哪种更类型安全,说出理由。
- 给 Toast 的 JS 版补上与 TS 对应的运行时校验(未知 kind 抛错、缺 retry 警告),体会「类型前移到运行时」的差别。
常见错误
- 什么都用 string:variant、size、kind 这类封闭集合必须用字面量联合;string 等于放弃检查。
- 可选项当万能补丁:所有 props 都加 ? 之后,非法组合全都不报错了;互斥关系用可辨识联合表达,不用可选堆叠。
- any 逃逸:回调参数标 any 后整条链路失守;先让泛型推断,推断不动再手写。
- 运行时数据直接信任类型:接口返回值需要运行时校验;类型断言(as)只是给编译器看的声明,不是防护网。
检查与复盘
自查清单:
- 能用可辨识联合表达一组互斥的 props 组合,说清判别字段的作用
- 泛型组件的回调参数类型自动跟随数据类型,不需要手写
- 记得类型的边界:编译期拦截加入口运行时校验,双保险
参考资料
- React 官方文档:使用 TypeScript —— 官方 TS 用法与组件类型约定
- TypeScript 手册:泛型 —— 泛型语法与推断
- React 官方文档:向组件传递 props —— props 设计的运行时语义
读完这一课了?
标记完成后,工作台会安排接下来的学习。