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:要求显式处理 null、undefined。
noImplicitAny:禁止隐式 any。
noUncheckedIndexedAccess:索引访问结果包含 undefined。
exactOptionalPropertyTypes:区分属性缺失与值为 undefined。
useUnknownInCatchVariables:catch 变量使用 unknown。
开启严格选项应配合渐进迁移,而不是用大量 as 和 ! 消除报错。
常见陷阱
非空断言
const root = document.querySelector('#root')!;
! 不生成运行时检查。只有当外部条件能保证值存在时才使用,否则应显式判断并给出错误。
枚举与字面量联合
纯前端业务常用字面量联合,类型简单且无额外运行时代码:
type Role = 'admin' | 'member';
需要反向映射、运行时对象或与既有协议对齐时再考虑 enum。跨端协议还要注意数值枚举成员变化带来的兼容风险。
类型体操过度
复杂类型应服务于公共 API,而不是追求零重复。若错误信息难以理解、编译明显变慢或调用者必须频繁断言,应降低抽象复杂度。
面试回答框架
- 说明类型约束要解决的业务问题。
- 区分编译期保证与运行时校验。
- 展示如何让非法状态不可表示。
- 说明类型精度、可读性和编译性能之间的取舍。