TypeScript 高级类型

一、keyof 与 typeof

keyof:获取对象类型的键联合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface User {
name: string;
age: number;
email: string;
}

type UserKeys = keyof User; // "name" | "age" | "email"

// 应用:类型安全的对象访问
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

// keyof 作用于非对象类型
type ArrayKeys = keyof [string, number]; // "0" | "1" | "length" | ...
type StringKeys = keyof string; // "length" | "charAt" | "slice" | ...

typeof:从值获取类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const user = { name: 'Alice', age: 25 };
type UserType = typeof user;
// { name: string; age: number }

// 与 ReturnType 配合:获取函数返回值
function createUser(name: string, age: number) {
return { id: Math.random(), name, age };
}

type CreatedUser = ReturnType<typeof createUser>;
// { id: number; name: string; age: number }

// typeof 用于 enum
enum Direction { Up, Down }
type Dir = keyof typeof Direction; // "Up" | "Down"

二、映射类型(Mapped Types)

映射类型遍历对象类型的键,对每个键应用变换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 基础语法:[P in K] 遍历 K 联合类型
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
type MyPartial<T> = { [P in keyof T]?: T[P] };
type MyRequired<T> = { [P in keyof T]-?: T[P] };

// 通过 as 重映射键(TS 4.1+)
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

interface Person {
name: string;
age: number;
}

type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

// 过滤键:通过 never 排除
type ExcludeNullProperties<T> = {
[P in keyof T as T[P] extends null ? never : P]: T[P];
};

// 应用:将对象中的所有函数类型转换为描述字符串
type FunctionDescriptions<T> = {
[P in keyof T as T[P] extends Function ? P : never]: string;
};

// 应用:事件监听器类型
type EventListeners<T> = {
[P in keyof T as `on${Capitalize<string & P>}`]?: (data: T[P]) => void;
};

type ClickEvent = { x: number; y: number };
type Events = { click: ClickEvent };
type Listeners = EventListeners<Events>;
// { onClick?: (data: ClickEvent) => void }

三、模板字面量类型(Template Literal Types)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Event = 'click' | 'focus' | 'scroll';
type Handler = `on${Capitalize<Event>}`;
// "onClick" | "onFocus" | "OnScroll"(注意:Capitalize 后首字母大写)

// 修正:用 Uncapitalize 保持事件名风格
type FixedHandler = `on${Capitalize<Event>}`;
// 实际上 "onClick" | "onFocus" | "OnScroll"——这里 Capitalize("scroll") = "Scroll"

// 多个占位符
type CSSValue = 'top' | 'left' | 'right' | 'bottom';
type MarginType = `margin${Capitalize<CSSValue>}`;
// "marginTop" | "marginLeft" | "marginRight" | "marginBottom"

// 结合泛型:类型安全的 API 路径
type ApiPath<Resource extends string, Action extends string>
= `/api/${Resource}/${Action}`;

type UserApi = ApiPath<'users', 'list'>; // "/api/users/list"

// 解析模板字面量(通过 infer 提取)
type ExtractId<T extends string> =
T extends `/user/${infer Id}` ? Id : never;

type UserId = ExtractId<'/user/123'>; // "123"

四、类型守卫(Type Guards)

typeof / instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function process(value: string | number) {
if (typeof value === 'string') {
return value.length; // narrowed to string
}
return value.toFixed(2); // narrowed to number
}

class Dog { bark() {} }
class Cat { meow() {} }

function speak(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}

自定义类型守卫(is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // narrowed to Fish
} else {
pet.fly(); // narrowed to Bird
}
}

类型谓词 is 的实际应用

1
2
3
4
5
6
7
// 过滤数组中的 null
function isNonNull<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
}

const items: (string | null)[] = ['a', null, 'b', null];
const valid = items.filter(isNonNull); // type: string[]

判别式联合(Discriminated Unions)

通过公共字段(kind / type)来区分联合类型的不同成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface Circle {
kind: 'circle';
radius: number;
}

interface Square {
kind: 'square';
sideLength: number;
}

interface Triangle {
kind: 'triangle';
base: number;
height: number;
}

type Shape = Circle | Square | Triangle;

function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
case 'triangle':
return shape.base * shape.height / 2;
}
}

never 实现穷举检查

1
2
3
4
5
6
7
8
9
10
11
12
function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}

function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.sideLength ** 2;
case 'triangle': return shape.base * shape.height / 2;
default: return assertNever(shape); // 如果新增了 Shape 成员未处理,此处编译报错
}
}

五、类型断言进阶

as const

将类型推断为最具体的字面量类型(所有属性变 readonly):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const colors = ['red', 'green', 'blue'] as const;
type Color = (typeof colors)[number]; // "red" | "green" | "blue"

const config = { theme: 'dark', version: 2 } as const;
// type: { readonly theme: "dark"; readonly version: 2 }

// 应用:定义枚举效果
const HTTP_STATUS = {
OK: 200,
NOT_FOUND: 404,
ERROR: 500,
} as const;

type StatusCode = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS]; // 200 | 404 | 500

// 枚举风格的 Map
const EVENT_TYPES = ['click', 'focus', 'blur'] as const;
type EventType = (typeof EVENT_TYPES)[number]; // "click" | "focus" | "blur"

satisfies(TS 4.9+)

satisfies保留原始类型推断的同时检查类型是否满足某个类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不用 satisfies:类型被放宽
const palette1: Record<string, string | string[]> = {
red: [255, 0, 0], // ✅ 但 red 被推断为 string | string[]
};
// palette1.red.map(...) ❌ 编译错误:不能确认是数组

// 用 satisfies:类型检查 + 保留原始推断
const palette2 = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;

palette2.red.map(x => x); // ✅ red 被推断为 number[]
palette2.green.toUpperCase(); // ✅ green 被推断为 string

六、Branded Types(名义类型模拟)

TypeScript 类型系统是结构化的(structural),不是名义化的(nominal)。但可以用 branded types 模拟名义类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Brand<T, B> = T & { __brand: B };

type UserId = Brand<number, 'UserId'>;
type OrderId = Brand<number, 'OrderId'>;

function getUserById(id: UserId) { /* ... */ }

const uid = 123 as UserId;
const oid = 456 as OrderId;

getUserById(uid); // ✅
getUserById(oid); // ❌ 编译错误:OrderId 不是 UserId
getUserById(123); // ❌ 编译错误:number 不是 UserId

七、面试题

Q1: 实现 ValueOf——获取对象所有值的联合类型

1
2
3
4
type ValueOf<T> = T[keyof T];

type User = { name: string; age: number; role: 'admin' | 'user' };
type UserValues = ValueOf<User>; // string | number | 'admin' | 'user'

Q2: 实现 GetterSetter 映射类型

1
2
3
4
5
6
7
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

type Setters<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (value: T[P]) => void;
};

Q3: 实现类型安全的 Object.keys

1
2
3
4
5
6
7
8
9
// 默认 Object.keys 返回 string[],丢失类型
function keys<T extends Record<string, any>>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}

const user = { name: 'Alice', age: 25 };
keys(user).forEach(k => {
// k 的类型是 "name" | "age" ✅
});

Q4: 实现 Merge——合并两个接口

1
2
3
4
5
6
7
8
9
10
11
12
type Merge<A, B> = {
[P in keyof (A & B)]: P extends keyof B
? B[P]
: P extends keyof A
? A[P]
: never;
};

type A = { a: string; b: number };
type B = { b: boolean; c: Date };
type Merged = Merge<A, B>;
// { a: string; b: boolean; c: Date }(B 的同名属性覆盖 A)

Q5: keyof any 是什么

1
2
type T = keyof any;  // string | number | symbol
// Record<K, T> 中 K extends keyof any 表示 K 只能为 string | number | symbol