MST

星途 面试题库

面试题:TypeScript 类型推断与高级类型编程

实现一个 `DeepPartial` 类型,它能将一个对象类型的所有属性及其子属性都变为可选。例如,给定类型 `{ a: { b: number } }`,`DeepPartial` 应返回 `{ a?: { b?: number } }`。在此过程中,详细阐述 TypeScript 的类型推断机制是如何工作的,以及如何利用条件类型、映射类型等高级类型特性来实现该功能。并且说明在处理深层次嵌套对象和循环引用对象时会遇到哪些挑战,如何解决。
23.0万 热度难度
前端开发TypeScript

知识考点

AI 面试

面试题答案

一键面试

1. 实现 DeepPartial 类型

type DeepPartial<T> = {
    [P in keyof T]?: DeepPartial<T[P]>;
};

2. TypeScript 类型推断机制

TypeScript 的类型推断是在编译时自动分析代码中值的类型的过程。当定义变量、函数参数、返回值等时,如果没有显式指定类型,TypeScript 会根据赋值的表达式来推断类型。例如:

let num = 10; // num 被推断为 number 类型
function add(a, b) {
    return a + b;
}
// add 函数的参数 a 和 b 被推断为 any 类型,返回值被推断为 any 类型

在泛型函数或类型中,类型推断根据传入的参数或使用场景来确定泛型类型。例如:

function identity<T>(arg: T): T {
    return arg;
}
let result = identity(5); // T 被推断为 number 类型

3. 利用条件类型、映射类型实现 DeepPartial

  • 映射类型:在 DeepPartial 中,[P in keyof T] 使用了映射类型。它遍历类型 T 的所有键,并为每个键创建一个新的属性。keyof T 获取类型 T 的所有键,P 代表每个键。
  • 条件类型:虽然在 DeepPartial 的基本实现中没有直接使用条件类型,但在更复杂的类型转换中可能会用到。条件类型的语法是 T extends U? X : Y,表示如果 T 可以赋值给 U,则类型为 X,否则为 Y。例如:
type IsString<T> = T extends string? true : false;
type StringCheck = IsString<string>; // true
type NumberCheck = IsString<number>; // false

DeepPartial 中,? 使属性变为可选,并且递归地应用 DeepPartial 到子属性,实现了深度可选化。

4. 处理深层次嵌套对象和循环引用对象的挑战及解决方法

  • 深层次嵌套对象
    • 挑战:随着嵌套层次的增加,类型计算的复杂度会指数级增长,可能导致编译时间变长。
    • 解决方法:可以通过控制嵌套深度来缓解,例如限制递归的层数。在某些场景下,如果嵌套深度已知,可以手动编写有限层的 DeepPartial 类型。例如,对于两层嵌套的对象:
type ShallowDeepPartial<T> = {
    [P in keyof T]?: {
        [Q in keyof T[P]]?: T[P][Q];
    };
};
  • 循环引用对象
    • 挑战:TypeScript 的类型系统是基于静态分析的,循环引用会导致类型检查陷入无限循环,无法得出结果。
    • 解决方法:在定义类型时避免循环引用。如果在运行时可能出现循环引用,可以在处理数据时通过额外的逻辑来检测和处理循环,例如使用 WeakMap 来记录已经处理过的对象,防止重复处理。在类型层面,可以通过引入中间类型,打破循环引用的直接关联。例如:
// 假设我们有两个相互引用的类型 A 和 B
// 错误示例(直接循环引用)
// type A = { b: B };
// type B = { a: A };

// 正确示例(通过中间类型打破循环)
type ARef = { b: BRef };
type BRef = { a: ARef };

type A = { b: B };
type B = { a: A };

这样在类型定义时避免了直接循环引用,同时在运行时可以通过 ARefBRef 来处理数据结构,防止无限循环。