Type-Fest 中的面试题:如何回答 TypeScript 类型相关问题
Type-Fest 中的面试题:如何回答 TypeScript 类型相关问题
在 TypeScript 面试中,类型转换、工具类型实现和复杂类型操作是常见考察点。本文基于开源项目 Type-Fest 的核心类型工具,通过实际面试场景解析如何高效运用这些工具解决问题。Type-Fest 作为「TypeScript 必备类型集合」,其源码 index.d.ts 定义了 190+ 常用类型,覆盖从基础类型到高级类型转换的全场景,是学习类型系统设计的优质资源。
一、类型剔除:Except 与 Omit 的核心差异
问题场景
面试官:"如何从接口 { id: number; name: string; age?: number } 中剔除 age 字段?为什么不用 Omit?"
分析与实现
Type-Fest 提供的 Except 类型解决了原生 Omit 的两个缺陷:严格键校验 和 索引签名兼容。其核心实现通过条件类型过滤键名:
// 核心逻辑:过滤掉指定键
type Filter<KeyType, ExcludeType> =
IsEqual<KeyType, ExcludeType> extends true ? never :
KeyType extends ExcludeType ? never : KeyType;
// 应用过滤并支持严格模式
export type Except<ObjectType, KeysType extends keyof ObjectType> = {
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType]
};
对比案例
原生 Omit 在处理带索引签名的对象时会丢失显式定义的键:
// 含索引签名的复杂对象
type UserData = {
[metadata: string]: string;
email: string;
name: string;
};
// Omit 结果:仅保留索引签名
type OmitResult = Omit<UserData, 'email'>;
// { [x: string]: string }
// Except 结果:保留显式键和索引签名
type ExceptResult = Except<UserData, 'email'>;
// { [x: string]: string; name: string }
测试用例 test-d/except.ts 验证了这一行为,当启用 requireExactProps: true 时,Except 会禁止多余键赋值,进一步增强类型安全性。
二、只读类型转换:Writable 的深层实现
问题场景
面试官:"如何将只读数组 readonly number[] 转换为可写数组?如何只解除部分属性的只读限制?"
实现解析
Writable 类型通过递归解构实现多层只读解除,核心代码:
// 数组类型处理
type WritableArray<ArrayType extends readonly unknown[]> =
ArrayType extends readonly [] ? [] :
ArrayType extends readonly [...infer U, infer V] ? [...U, V] :
ArrayType extends ReadonlyArray<infer U> ? U[] : ArrayType;
// 主类型定义
export type Writable<BaseType, Keys extends keyof BaseType = keyof BaseType> =
BaseType extends ReadonlyMap<infer K, infer V> ? Map<K, V> :
BaseType extends ReadonlySet<infer T> ? Set<T> :
BaseType extends readonly unknown[] ? WritableArray<BaseType> :
Simplify<Except<BaseType, Keys> & {-readonly [K in Keys]: BaseType[K]}>;
关键特性
- 精准控制:支持指定部分键解除只读,如
Writable<{ readonly a: number; b: string }, 'a'> - 容器兼容:处理
ReadonlyMap、ReadonlySet等内置容器 - 数组特殊处理:通过解构保留元组长度信息,如
Writable<readonly [1, 2]>结果为[1, 2]
测试用例 test-d/writable.ts 验证了这些场景,特别是元组和索引签名对象的转换行为。
三、高级类型转换:UnionToIntersection 的魔法
问题场景
面试官:"如何将联合类型 { a: string } | { b: number } 转换为交叉类型 { a: string } & { b: number }?"
原理与实现
UnionToIntersection 利用 TypeScript 条件类型的分配性和函数参数逆变特性实现转换:
export type UnionToIntersection<Union> = (
Union extends unknown ? (k: Union) => void : never
) extends ((k: infer Intersection) => void) ? Intersection & Union : never;
实战案例
// 联合转交叉
type Union = { id: number } | { name: string };
type Intersection = UnionToIntersection<Union>;
// { id: number } & { name: string }
// 函数类型合并
type FnUnion = ((a: string) => void) | ((b: number) => void);
type FnIntersection = UnionToIntersection<FnUnion>;
// (a: string) => void & (b: number) => void
测试用例 test-d/union-to-intersection.ts 验证了复杂联合类型的转换结果,包括含冲突属性的联合类型处理。
四、工程化实践:Type-Fest 的类型设计哲学
源码组织与最佳实践
Type-Fest 的源码结构 source/ 采用功能模块化设计,核心原则包括:
- 单一职责:每个类型工具独立文件,如 source/union-to-intersection.d.ts 专注联合转交叉
- 测试驱动:每个类型配备对应测试文件,如 test-d/ 目录下的验证用例
- 渐进增强:基础类型(如 Primitive)构建复杂类型(如 Jsonifiable)
常见面试扩展问题
- 性能考量:深度递归类型(如 PartialDeep)可能导致类型检查延迟,需控制嵌套深度
- 版本兼容性:部分类型(如 IntRange)依赖 TypeScript 4.5+ 的递归条件类型
- 自定义工具类型:组合现有类型实现业务需求,如
type RequiredAndWritable<T> = Writable<Required<T>>
总结与学习路径
掌握 Type-Fest 中的类型工具不仅能应对面试挑战,更能提升日常开发效率。建议学习路径:
- 基础类型:Primitive → JsonValue
- 中级转换:Except → Writable
- 高级操作:UnionToIntersection → ConditionalPickDeep
通过阅读 README.md 文档和源码注释,结合实际场景练习,可系统提升 TypeScript 类型系统驾驭能力。记住:优秀的类型设计应当像 Type-Fest 这样,既强大又易用。
更多推荐

所有评论(0)