返回专题首页

React 专题

TypeScript 与 React 协作:组件类型、事件类型与泛型坑位

React 和 TypeScript 的结合之所以重要,不是因为“写起来更规范”,而是因为一旦项目进入组件复用、复杂 props、事件协作、自定义 Hook 和高频状态流,类型边界会直接影响可维护性。很多 React 项目真正开始稳起来,往往就是从类型边界开始的。

React 专题第 14 篇 / 26 篇4 分钟

React 和 TypeScript 的结合之所以重要,不是因为“写起来更规范”,而是因为一旦项目进入组件复用、复杂 props、事件协作、自定义 Hook 和高频状态流,类型边界会直接影响可维护性。很多 React 项目真正开始稳起来,往往就是从类型边界开始的。

这一篇重点不只是讲几个类型写法,而是帮助你建立一套更成熟的 React + TS 协作心智。

为什么 React 项目里类型边界这么重要?

因为 React 组件的核心就是输入和输出,而 TypeScript 正好能帮助你把这件事提前表达清楚。更具体地说,它能帮助你明确:

  • 组件接收哪些 props;
  • 事件回调希望拿到什么数据;
  • 自定义 Hook 暴露什么能力;
  • 泛型组件到底在抽象什么。

如果这些边界不清楚,组件虽然能跑,但复用和协作成本会很快上升。

组件类型最值得先想清楚什么?

不是“语法怎么写”,而是:

  • 这个组件是展示型还是承接业务协作型;
  • 它的 props 有没有表达清楚职责;
  • 哪些 props 是必须的,哪些是可选的;
  • 回调是在表达动作,还是在泄漏内部细节。

也就是说,TypeScript 在 React 里最重要的不是补提示,而是帮助你把组件接口设计得更稳。

事件类型为什么总是高频痛点?

因为 React 项目里大量交互都从事件开始,而事件又经常和表单、列表、选择器、自定义组件协作绑在一起。常见问题包括:

  • 输入事件值拿不清;
  • 回调参数定义不统一;
  • 自定义组件事件和原生事件混在一起;
  • 为了图省事全部写成 any

更成熟的做法通常是:把事件类型也看成组件接口的一部分,而不是临时补丁。

泛型在 React 里最容易在哪些地方出现?

比较高频的包括:

  • 通用列表组件;
  • 表格列定义;
  • 表单字段封装;
  • 自定义 Hook 返回值;
  • 某些受控组件和异步选择器。

但最重要的是:泛型在 React 里不是为了“显得高级”,而是为了在复用时仍然保有稳定类型边界。如果一个泛型组件让调用方看不懂,那通常说明抽象已经过度。

为什么很多 React + TS 代码明明有类型,却还是不好维护?

因为问题不在有没有类型,而在类型是否真正服务了边界设计。常见坏味道包括:

  • props 很多,但名字和职责都模糊;
  • 所有复杂地方都回退成 any
  • 泛型参数太多,阅读成本极高;
  • 事件和状态结构没有统一约定;
  • 自定义 Hook 暴露过多内部细节。

所以 TypeScript 的价值不在“覆盖率”,而在类型是不是让边界更稳定。

把 TypeScript 放回 React 项目里,最重要的判断是什么?

一个更稳的顺序通常是:

1. 先设计组件输入输出边界;2. 再决定类型如何表达这些边界;3. 泛型只在复用收益明确时引入;4. 事件类型、表单类型、数据模型尽量保持统一来源;5. 避免为了暂时省事让 any 一路扩散。

最常见的几个误区

1. 组件先写完,再补类型

这样往往只是给混乱接口套一层壳。

2. 复杂场景直接 any

短期快,长期会持续侵蚀类型收益。

3. 泛型抽象过度

调用方很快就会因为阅读成本而回避复用。

4. 事件和回调参数没有统一约定

后面协作会越来越乱。

总结

TypeScript 与 React 协作真正要建立的,不是更多写法技巧,而是“组件接口、事件协作、自定义 Hook 和复用边界怎样被稳定表达”这套能力。只要你始终先想边界,再用类型把边界说清楚,React + TS 才会真正开始提升项目可维护性。