说说你对 TypeScript 中高级类型的理解?有哪些?
问题解析
本题考察对 TypeScript 高级类型特性的掌握程度,包括交叉类型、联合类型、映射类型、条件类型等。这些特性是 TypeScript 类型系统的核心,能够应对复杂多变的开发场景。
核心概念
- 交叉类型(Intersection Types):将多个类型合并为一个类型
- 联合类型(Union Types):表示值可以是多种类型之一
- 类型别名(Type Aliases):为类型创建新名字
- 类型索引(keyof):获取对象类型的所有键
- 类型约束(extends):对泛型进行约束
- 映射类型(Mapped Types):基于旧类型创建新类型
- 条件类型(Conditional Types):根据条件选择类型
详细解答
1. 是什么
除了 string、number、boolean 这种基础类型外,在 TypeScript 类型声明中还存在一些高级的类型应用。
这些高级类型,是 TypeScript 为了保证语言的灵活性,所使用的一些语言特性。这些特性有助于我们应对复杂多变的开发场景。
2. 高级类型详解
2.1 交叉类型(Intersection Types)
通过 & 将多个类型合并为一个类型,包含了所需的所有类型的特性,本质上是一种并的操作。
语法:T & U
适用于对象合并场景:
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let key in first) {
(<any>result)[key] = (<any>first)[key];
}
for (let key in second) {
if (!result.hasOwnProperty(key)) {
(<any>result)[key] = (<any>second)[key];
}
}
return result;
}
// 使用示例
interface Person {
name: string;
}
interface Contact {
phone: string;
}
const personWithContact: Person & Contact = {
name: "Tom",
phone: "123456789"
};
2.2 联合类型(Union Types)
联合类型的语法规则和逻辑"或"的符号一致,表示其类型为连接的多个类型中的任意一个,本质上是一个交的关系。
语法:T | U
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
return line;
}
// 使用示例
let value: string | number;
value = "hello"; // ok
value = 123; // ok
// value = true; // error
2.3 类型别名(Type Aliases)
类型别名会给一个类型起个新名字,类型别名有时和接口很像,但是可以作用于原始值、联合类型、元组以及其它任何你需要手写的类型。
// 基础类型别名
type some = boolean | string;
const b: some = true; // ok
const c: some = 'hello'; // ok
// const d: some = 123; // 不能将类型"123"分配给类型"some"
// 泛型类型别名
type Container<T> = { value: T };
// 自引用类型别名
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
};
// 与接口的区别
// interface 只能用于定义对象类型
type Point = {
x: number;
y: number;
};
// type 可以定义联合类型、交叉类型、原始类型等
type ID = string | number;
type Coordinates = Point & { z: number };
interface vs type:
interface只能用于定义对象类型type的声明方式除了对象之外还可以定义交叉、联合、原始类型等,类型声明的方式适用范围更加广泛
2.4 类型索引(keyof)
keyof 类似于 Object.keys,用于获取一个接口中 Key 的联合类型。
interface Button {
type: string;
text: string;
}
type ButtonKeys = keyof Button;
// 等效于
type ButtonKeys = "type" | "text";
// 实际应用
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const button: Button = { type: "primary", text: "Click" };
const typeValue = getProperty(button, "type"); // string
2.5 类型约束(extends)
通过关键字 extends 进行约束,不同于在 class 后使用 extends 的继承作用,泛型内使用的主要作用是对泛型加以约束。
type BaseType = string | number | boolean;
// 参数只能是字符串、数字、布尔这几种基础类型
function copy<T extends BaseType>(arg: T): T {
return arg;
}
// 类型约束通常和类型索引一起使用
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key]; // ok
}
const obj = { a: 1, b: "hello" };
const a = getValue(obj, 'a'); // 1
const b = getValue(obj, 'b'); // "hello"
// const c = getValue(obj, 'c'); // Error: 类型"c"的参数不能赋给类型"a" | "b"的参数
2.6 映射类型(Mapped Types)
通过 in 关键字做类型的映射,遍历已有接口的 key 或者是遍历联合类型。
// 内置的 Readonly 类型实现
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Obj {
a: string;
b: string;
}
type ReadOnlyObj = Readonly<Obj>;
// 等效于:
// interface ReadOnlyObj {
// readonly a: string;
// readonly b: string;
// }
// 其他内置映射类型
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
};
映射类型的执行步骤:
keyof T:通过类型索引keyof得到联合类型'a' | 'b'P in keyof T等同于P in 'a' | 'b',相当于执行了一次forEach的逻辑
2.7 条件类型(Conditional Types)
条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。
语法:T extends U ? X : Y
意思是:如果 T 是 U 的子集,就是类型 X,否则为类型 Y。
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 实际应用:根据类型选择处理函数
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
// 条件类型与映射类型结合
type NonNullable<T> = T extends null | undefined ? never : T;
3. 高级类型的实际应用
工具类型实现
// DeepReadonly:深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// Nullable:可为 null
type Nullable<T> = T | null;
// ReturnType:获取函数返回类型
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
// Parameters:获取函数参数类型
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
深入理解
infer 关键字
infer 用于在条件类型中声明类型变量,用于推断类型。
// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type NumArray = number[];
type Num = ElementType<NumArray>; // number
// 提取 Promise 的返回值类型
type PromiseType<T> = T extends Promise<infer U> ? U : never;
type PromiseString = Promise<string>;
type Str = PromiseType<PromiseString>; // string
分布式条件类型
当条件类型作用于联合类型时,会分布式地应用于联合类型的每个成员。
type ToArray<T> = T extends any ? T[] : never;
// 分布式展开
type StrOrNumArray = ToArray<string | number>;
// 等效于:string[] | number[]
// 阻止分布式行为
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type ArrayOfStrOrNum = ToArrayNonDist<string | number>;
// 等效于:(string | number)[]
最佳实践
- 优先使用内置工具类型:TypeScript 提供了丰富的内置工具类型(
Partial、Required、Readonly、Pick、Record等) - 合理使用类型别名:复杂类型使用
type定义,提高可读性 - 善用条件类型:处理动态类型场景,如 API 响应类型转换
- 避免过度复杂化:类型是为了提高开发效率,过于复杂的类型会降低可维护性
面试要点
- 交叉类型
&:合并多个类型,取并集 - 联合类型
|:可以是多种类型之一 - 类型别名
type:vs 接口,适用范围更广 - keyof:获取对象所有键的联合类型
- extends:类型约束和条件类型
- 映射类型:基于旧类型创建新类型,如
Readonly<T>、Partial<T> - 条件类型:
T extends U ? X : Y,支持infer推断 - 内置工具类型:熟悉
Partial、Required、Pick、Record、ReturnType等的实现原理