TypeScript 工程实践与常见陷阱

接口响应如何建模

不要把后端响应直接断言成业务类型。静态类型无法验证运行时 JSON。

type ApiResponse<T> = {
  code: number;
  message: string;
  data: T;
};

async function requestJson(url: string): Promise<unknown> {
  const response = await fetch(url);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

推荐流程是:外部数据以 unknown 进入,在 API 边界使用 schema 或守卫校验,再转换为领域类型。

避免布尔值组合出非法状态

// 容易同时出现 loading=true、success=true
type BadState = {
  loading: boolean;
  success: boolean;
  error?: Error;
};

type State<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'failure'; error: Error };

让类型排除非法状态,比依赖注释和运行时约定更可靠。

strict 相关配置

项目至少应重点理解:

  • strictNullChecks:要求显式处理 nullundefined
  • noImplicitAny:禁止隐式 any
  • noUncheckedIndexedAccess:索引访问结果包含 undefined
  • exactOptionalPropertyTypes:区分属性缺失与值为 undefined
  • useUnknownInCatchVariablescatch 变量使用 unknown

开启严格选项应配合渐进迁移,而不是用大量 as! 消除报错。

常见陷阱

非空断言

const root = document.querySelector('#root')!;

! 不生成运行时检查。只有当外部条件能保证值存在时才使用,否则应显式判断并给出错误。

枚举与字面量联合

纯前端业务常用字面量联合,类型简单且无额外运行时代码:

type Role = 'admin' | 'member';

需要反向映射、运行时对象或与既有协议对齐时再考虑 enum。跨端协议还要注意数值枚举成员变化带来的兼容风险。

类型体操过度

复杂类型应服务于公共 API,而不是追求零重复。若错误信息难以理解、编译明显变慢或调用者必须频繁断言,应降低抽象复杂度。

面试回答框架

  1. 说明类型约束要解决的业务问题。
  2. 区分编译期保证与运行时校验。
  3. 展示如何让非法状态不可表示。
  4. 说明类型精度、可读性和编译性能之间的取舍。