TypeScript 面试题:类型系统、泛型、高级类型与工具类型
TypeScript 面试重点不是会不会写类型标注,而是能否用类型系统表达约束、减少运行时错误,并理解类型推导的边界。
类型系统基础
any、unknown 与 never
知识点讲解
any 会关闭类型检查,适合迁移期兜底但不建议滥用。unknown 表示未知类型,使用前必须做类型收窄,更安全。never 表示永远不会出现的值,常用于穷尽检查和抛错函数。
面试常问
问:any 和 unknown 区别?
答:any 可以赋值给任意类型,也可以任意访问属性,等于放弃检查;unknown 只能赋值给 unknown 或 any,使用前必须判断类型。
类型推断与类型收窄
知识点讲解
TS 会根据初始化值、函数返回值、控制流判断推导类型。常见收窄方式包括 typeof、instanceof、in、判空、字面量判断、自定义类型守卫。
面试常问
问:什么是类型守卫?
答:类型守卫是在运行时做判断,并让 TS 在分支内收窄类型的机制。比如 function isString(x: unknown): x is string。
泛型
泛型的价值
知识点讲解
泛型让类型像参数一样传递,既保留灵活性,又不丢失类型信息。比如数组取第一个元素,返回值应该和数组元素类型一致,而不是写死成某个类型。
面试常问
问:泛型和 any 有什么区别?
答:泛型保留输入和输出之间的类型关系,any 会丢失关系。泛型是安全的抽象,any 是逃过检查。
泛型约束
知识点讲解
泛型约束用 extends 限制类型参数必须满足某种结构。比如访问 length 时,可以写 T extends { length: number },否则 TS 不知道 T 是否有该属性。
面试常问
问:keyof T 常用在哪里?
答:常用于约束对象属性名。比如 getValue<T, K extends keyof T>(obj: T, key: K): T[K],可以保证 key 一定存在,返回值类型也精确。
高级类型
条件类型与 infer
知识点讲解
条件类型形如 T extends U ? X : Y。infer 可以在条件类型中推断某一部分类型,比如从函数类型中推断返回值。
面试常问
问:如何实现 ReturnType?
答:思路是判断 T 是否是函数类型,如果是,用 infer R 捕获返回值类型:T extends (...args: any[]) => infer R ? R : never。
映射类型
知识点讲解
映射类型可以遍历对象类型的 key,批量改变属性。Partial、Readonly、Pick 等工具类型都基于映射类型实现。
面试常问
问:如何实现 Partial?
答:遍历 keyof T,把每个属性加上可选修饰:type MyPartial<T> = { [K in keyof T]?: T[K] }。
联合与交叉
知识点讲解
联合类型表示“可能是其中一种”,使用前通常需要收窄。交叉类型表示“同时满足多个类型”。业务里可用可辨识联合建模状态,比如 loading、success、error。
面试常问
问:为什么可辨识联合适合状态建模?
答:因为每个状态都有明确 type 字段,TS 能根据分支自动收窄,避免访问不存在的数据。
工程实践
工具类型
知识点讲解
常见工具类型包括 Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、ReturnType、Parameters。面试要知道作用,也要能讲出实现思路。
面试常问
问:Pick 和 Omit 区别?
答:Pick<T, K> 从类型中选出指定 key;Omit<T, K> 从类型中排除指定 key。前者做白名单,后者做黑名单。
tsconfig 常见配置
知识点讲解
strict 是类型安全总开关;noImplicitAny 禁止隐式 any;strictNullChecks 区分 null/undefined;paths 可以配置路径别名;skipLibCheck 会跳过声明文件检查以提升构建速度。
面试常问
问:为什么推荐开启 strict?
答:它能让类型系统更早暴露空值、隐式 any、函数参数等问题。迁移老项目可以分阶段开启,但新项目建议默认开启。
底层追问与代码示例
类型系统是结构化类型
知识点讲解
TypeScript 使用结构化类型系统,判断兼容性时看结构,不看声明来源。这和 Java、C# 这类名义类型系统不同。
1 | type User = { id: number; name: string } |
如果业务上需要区分相同结构的不同概念,可以用品牌类型:
1 | type Brand<T, B extends string> = T & { readonly __brand: B } |
分布式条件类型
知识点讲解
当条件类型左侧是裸类型参数时,传入联合类型会自动分发。
1 | type ToArray<T> = T extends unknown ? T[] : never |
如果不想分发,可以用元组包住:
1 | type NoDistribute<T> = [T] extends [unknown] ? T[] : never |
面试常问
问:为什么 Exclude 能过滤联合类型?
答:因为它依赖分布式条件类型,对联合类型中的每一项分别判断。
1 | type MyExclude<T, U> = T extends U ? never : T |
实用类型体操
知识点讲解
中高级面试常要求你写出几个核心工具类型。
1 | type MyPick<T, K extends keyof T> = { |
as 在映射类型中可以重映射 key,因此能实现 Omit、字段改名、过滤字段。
函数重载与泛型约束
知识点讲解
函数重载适合“输入不同,输出也不同”的 API;泛型适合“输入和输出存在类型关系”的 API。
1 | function query(id: number): { id: number; type: 'single' } |
面试常问
问:为什么实现签名不能直接暴露?
答:调用方看到的是重载签名,最后一个实现签名只负责兼容所有重载分支。这样能给调用方更精确的类型。