Skip to content

TypeScript 的类型边界

TypeScript 不会让运行时数据自动变安全。它擅长描述已经可信的结构、约束模块之间的约定;来自接口、表单和存储的数据仍需要在运行时验证。

先让函数输入和输出清楚

类型最有价值的地方是公共边界。为参数、返回值和领域对象命名,调用方就不必翻实现猜测数据形状。

ts
type Member = {
  id: string
  name: string
  role: 'reader' | 'editor'
}

function canEdit(member: Member): boolean {
  return member.role === 'editor'
}

避免为了省事把业务数据写成 any。一旦进入 any,后续属性访问和赋值都失去检查,类型系统无法再帮你发现拼写或结构错误。

用联合类型表达有限状态

页面状态通常不是一堆互不关联的布尔值,而是有限集合。可辨识联合能让每个状态携带自己需要的数据。

ts
type LoadState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; message: string }

function renderState(state: LoadState<Member>) {
  if (state.status === 'success') return state.data.name
  if (state.status === 'error') return state.message
  return '请稍候'
}

相比 loadingerrordata 同时存在的松散对象,这种写法能减少“不可能组合”,例如既加载成功又没有数据。

unknownany 更适合外部输入

接口响应先是未知数据。使用 unknown 后,必须经过判断才能访问属性,这迫使我们在边界处完成验证。

ts
function isMember(value: unknown): value is Member {
  if (!value || typeof value !== 'object') return false

  const record = value as Record<string, unknown>
  return typeof record.id === 'string'
    && typeof record.name === 'string'
    && (record.role === 'reader' || record.role === 'editor')
}

复杂接口可以使用统一的解析层或 schema 工具;重点是验证只能发生一次,并在失败时给出足够的上下文。

类型设计的三个边界

边界关注点典型做法
外部输入数据是否可信unknown、运行时校验、错误转换
模块接口调用双方如何协作导出的 type、参数和返回值
领域模型状态是否允许组合联合类型、只读字段、品牌类型

类型不是数据模型的替代品。先定义真实业务概念,再让类型帮助它保持一致,代码会比“到处补断言”更稳定。

为复用而记录,为理解而整理。