TypeScript 进阶学习笔记
适合目标:5 小时内快速建立 TypeScript 进阶类型系统框架,覆盖高级类型、工具类型实现、类型体操与高频面试题。
学习重点:泛型约束、条件类型、映射类型、模板字面量类型、工具类型实现、类型体操题。
学习原则:先理解“类型在做什么”,再去背写法;先会看懂,再会自己实现;先掌握套路,再练复杂题。
目录
- 学习总览
- TypeScript 类型系统基础补强
- 高级类型
- 工具类型实现
- 类型体操面试题
- 实战场景与思维方式
- 高频面试题
- 5 小时学习节奏建议
- 一页速记总结
- 背诵口诀
1. 学习总览
1.1 TypeScript 到底在学什么
很多人学 TypeScript 会有一种感觉:
- 代码看起来像 JavaScript,但又多了很多陌生语法
- 类型语法能看懂一点,但自己写就容易卡
- 工具类型会用,换个题就不会实现
本质上,TypeScript 学的不是“语法堆积”,而是下面这套思维:
值的世界 -> 类型的世界 -> 类型之间如何推导 -> 如何约束和转换类型
也就是说,你要慢慢形成一个认知:
- JavaScript 在运行值
- TypeScript 在编译阶段分析类型
- 很多“高级类型”本质是对类型做判断、遍历、拼接、提取、过滤
1.2 你可以把 TS 类型系统看成什么
可以把 TypeScript 类型系统理解成:
一个只在编译期运行的小型逻辑系统
它最常做的事情只有几类:
- 接收一个类型
- 判断它是否满足某条件
- 把它映射成另一个类型
- 从里面提取一部分信息
- 把多个类型拼起来生成新类型
1.3 核心主线
主线 1:泛型解决“类型如何复用”
<T>:把类型当参数传进去extends:给泛型加约束= 默认值:给泛型默认类型
主线 2:条件类型解决“类型如何判断”
T extends U ? X : Y- 像
if...else,但判断的是类型 - 常与
infer配合做提取
主线 3:映射类型解决“类型如何遍历”
[K in keyof T]- 遍历对象每个 key
- 常用于实现
Partial、Readonly、Pick
主线 4:模板字面量类型解决“类型如何拼接字符串”
`prefix${T}`- 常用于事件名、路由名、状态名推导
主线 5:类型体操解决“综合推导”
- 深度递归
- 联合分发
- 元组拆解
- 链式调用累积类型
2. TypeScript 类型系统基础补强
你给的重点是进阶内容,但如果基础概念没稳,后面会越看越绕。所以这里先补齐最关键的地基,只保留和进阶题最相关的部分。
2.1 类型和值要分开理解
TypeScript 里最容易混淆的一点是:
- 值存在于运行时
- 类型存在于编译时
示例:
const name = "tom";
type Name = string;
这里:
name是值Name是类型
TypeScript 会在编译时检查 name 是否符合 Name 之类的规则,但运行时不会保留这些类型信息。
2.2 常见基础类型要非常清楚
any
any 表示关闭类型检查,尽量少用。
let value: any = 123;
value = "abc";
value.foo.bar.baz();
问题:
- 可以赋值给任何类型
- 也可以接收任何类型
- 等于放弃了 TypeScript 的价值
unknown
unknown 表示“未知类型”,比 any 安全。
let value: unknown = "hello";
if (typeof value === "string") {
console.log(value.toUpperCase());
}
区别:
any可以直接乱用unknown必须先缩小类型后再使用
never
never 表示“永远不会有值”。
常见场景:
- 永远抛错的函数
- 死循环函数
- 条件类型中过滤分支
- 穷尽性检查
function throwError(message: string): never {
throw new Error(message);
}
void
void 表示函数没有返回值。
function log(message: string): void {
console.log(message);
}
2.3 联合类型与交叉类型
联合类型
type Status = "success" | "error" | "loading";
含义:
值可能是其中之一。
交叉类型
type A = { name: string };
type B = { age: number };
type C = A & B;
含义:
C 同时拥有 A 和 B 的结构。
2.4 type 和 interface 区别
type
更灵活,适合:
- 联合类型
- 交叉类型
- 条件类型
- 工具类型实现
interface
更偏向描述对象结构,支持声明合并。
interface User {
name: string;
}
interface User {
age: number;
}
const u: User = {
name: "tom",
age: 18
};
面试怎么答
- 两者都能描述对象结构
type更适合复杂类型表达式interface支持声明合并,扩展对象语义更自然- 实际项目里通常对象优先
interface,复杂组合优先type
2.5 keyof、typeof、in、索引访问
这些是后面所有高级类型的基础。
keyof
获取对象类型的 key 联合。
type User = {
name: string;
age: number;
};
type UserKeys = keyof User; // "name" | "age"
typeof
在类型上下文里获取某个值的类型。
const user = {
name: "tom",
age: 18
};
type User = typeof user;
索引访问类型
type User = {
name: string;
age: number;
};
type NameType = User["name"]; // string
in
在映射类型中遍历 key。
type Copy<T> = {
[K in keyof T]: T[K];
};
2.6 extends 在 TS 里有两种常见含义
这个点很容易混。
含义 1:泛型约束
type GetName<T extends { name: string }> = T["name"];
表示:
T 必须满足某结构。
含义 2:条件判断
type IsString<T> = T extends string ? true : false;
表示:
如果 T 可以赋给 string,返回 true,否则 false。
记忆口诀
写在尖括号里,多半是约束;写在问号前面,多半是判断。
3. 高级类型
这一部分是 TypeScript 进阶的核心,也是你图片里第一大块的重点。
3.1 泛型
3.1.1 什么是泛型
泛型就是“把类型参数化”,让类型像函数参数一样可复用。
function identity<T>(value: T): T {
return value;
}
const a = identity<string>("hello");
const b = identity(123);
理解:
T不是具体类型- 调用时再由传入值推导或显式指定
- 让函数、接口、类型别名具备复用能力
3.1.2 泛型为什么重要
如果不用泛型,你常常只能:
- 写死类型
- 或者写成
any
泛型的价值就是:
既灵活,又保留类型信息
3.1.3 泛型约束
你图片里的重点之一就是:
<T extends Constraint>
含义:
泛型参数 T 必须满足某种结构或范围。
示例:
type GetLength<T extends { length: number }> = T["length"];
这个类型要求:
T 必须有 length 属性。
函数版示例:
function getLength<T extends { length: number }>(value: T) {
return value.length;
}
getLength("abc");
getLength([1, 2, 3]);
// getLength(123); // 报错
3.1.4 keyof 约束
这是面试高频:
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
理解:
T表示对象类型K extends keyof T表示 key 必须是对象已有的键- 返回值
T[K]表示对应属性值类型
3.1.5 泛型默认值
图片里有“泛型默认值怎么写”,标准写法是:
type Box<T = string> = {
value: T;
};
使用:
type A = Box; // 默认 T = string
type B = Box<number>;
泛型默认值常见场景:
- 减少调用方书写成本
- 提供常用默认类型
- 让 API 更友好
3.1.6 多个泛型参数
type Pair<T, U = T> = {
first: T;
second: U;
};
这里 U = T 表示:
如果没传第二个类型参数,就默认和 T 一样。
3.1.7 泛型记忆口诀
泛型是类型的变量,extends 负责加门槛,= 负责给默认。
3.2 条件类型
3.2.1 条件类型是什么
条件类型就是类型系统里的 if...else。
写法:
T extends U ? X : Y
含义:
如果 T 可以赋值给 U,结果就是 X,否则是 Y。
3.2.2 最简单例子
type IsString<T> = T extends string ? true : false;
type A = IsString<"abc">; // true
type B = IsString<number>; // false
3.2.3 实现 If 条件类型
这是面试常见题:
type If<C extends boolean, T, F> = C extends true ? T : F;
type A = If<true, "yes", "no">; // "yes"
type B = If<false, "yes", "no">; // "no"
3.2.4 条件类型的本质
它通常用来做四件事:
- 判断类型是否满足某条件
- 根据判断结果返回不同类型
- 过滤联合类型
- 配合
infer提取类型
3.2.5 分发条件类型
这是 TypeScript 进阶里最关键的点之一。
当条件类型左边是“裸类型参数”并且传入联合类型时,会自动分发。
示例:
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
结果:
type Result = string[] | number[];
因为它被拆成:
type Result = ToArray<string> | ToArray<number>;
3.2.6 如何阻止分发
把类型参数包起来:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNonDist<string | number>; // (string | number)[]
3.2.7 Exclude 和 Extract 的本质
type MyExclude<T, U> = T extends U ? never : T;
type MyExtract<T, U> = T extends U ? T : never;
理解:
Exclude是把满足条件的剔掉Extract是把满足条件的留下
3.2.8 条件类型记忆口诀
条件类型像 if,联合类型会分发,never 常用来过滤。
3.3 infer 类型推断
3.3.1 infer 是什么
infer 可以在条件类型中“声明一个待推导的类型变量”,用来从某个复杂类型里提取一部分信息。
你可以把它理解成:
条件成立时,顺手把内部某段类型抓出来
3.3.2 提取函数返回值
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
解释:
- 先约束
T必须是函数 - 如果
T形如(...args) => 某返回值 - 那就把返回值推断成
R - 最终返回
R
使用:
type Fn = (name: string) => number;
type Result = MyReturnType<Fn>; // number
3.3.3 提取参数类型
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
结果是元组类型。
type Fn = (name: string, age: number) => boolean;
type Params = MyParameters<Fn>; // [name: string, age: number]
3.3.4 提取 Promise 内部值
type MyAwaited<T> = T extends Promise<infer R> ? R : T;
更完整的递归版:
type MyAwaitedDeep<T> =
T extends Promise<infer R> ? MyAwaitedDeep<R> : T;
3.3.5 从元组中取第一项
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
3.3.6 infer 记忆口诀
infer 只在条件类型里用,作用是把类型里的某部分抠出来。
3.4 映射类型
3.4.1 映射类型是什么
映射类型就是:
遍历一个对象类型的所有 key,生成新的对象类型
基本写法:
type Copy<T> = {
[K in keyof T]: T[K];
};
3.4.2 Partial 的本质
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
作用:
把对象所有属性都变成可选。
3.4.3 Readonly 的本质
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
3.4.4 Required 的本质
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
这里 -? 表示移除可选修饰符。
3.4.5 可选和只读修饰符操作
TypeScript 映射类型里常见修饰符:
?:加可选-?:去可选readonly:加只读-readonly:去只读
示例:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
3.4.6 Pick 的实现
图片里给的手写题之一就是:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
理解:
K只能是T的 key- 遍历选中的
K - 值类型仍取
T[P]
3.4.7 Omit 的实现
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};
理解:
keyof T取出全部 keyExclude<keyof T, K>去掉要省略的部分- 再重新映射回对象类型
3.4.8 Record 的实现
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
说明:
K可以是字符串、数字、symbol 这类合法 key- 每个 key 的值都统一为
T
3.4.9 映射类型记忆口诀
keyof 先拿键,in 去遍历,T[K] 拿值,修饰符控制可选和只读。
3.5 模板字面量类型
3.5.1 什么是模板字面量类型
模板字面量类型允许你像拼字符串一样拼类型。
type EventName<T extends string> = `on${Capitalize<T>}`;
type A = EventName<"click">; // "onClick"
3.5.2 为什么它重要
它特别适合处理:
- 事件名推导
- CSS 类名推导
- 路由 key 推导
- getter/setter 命名
- API 前缀拼接
3.5.3 事件名推导示例
type Events = "click" | "change" | "focus";
type HandlerNames = `on${Capitalize<Events>}`;
结果:
type HandlerNames = "onClick" | "onChange" | "onFocus";
3.5.4 结合 keyof 使用
type Watchers<T> = {
[K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
};
示例:
type User = {
name: string;
age: number;
};
type UserWatchers = Watchers<User>;
会得到:
type UserWatchers = {
onNameChange: (value: string) => void;
onAgeChange: (value: number) => void;
};
3.5.5 内置字符串工具类型
Uppercase<T>Lowercase<T>Capitalize<T>Uncapitalize<T>
3.5.6 模板字面量类型记忆口诀
模板字面量类型就是把字符串拼接能力搬到了类型系统。
3.6 高级类型串联理解
把这些内容串起来,你会发现很多题目本质都只有一个套路:
- 先拿到 key 或结构
- 再判断是否满足条件
- 必要时用
infer提取内部类型 - 最后用映射或模板字面量生成新类型
举个综合例子:
type EventMap<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};
这里同时用到了:
keyof- 映射类型
as重映射 key- 模板字面量类型
Capitalize
4. 工具类型实现
这一部分是你图片里的第二大块,也是面试里最容易考“手写”的内容。
4.1 为什么要学工具类型实现
因为会“用”和会“实现”是两个层次:
- 会用说明你知道场景
- 会实现说明你理解类型系统底层套路
只要你掌握了工具类型的实现思路,很多类型体操题都会变简单。
4.2 Pick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
记忆拆解:
K extends keyof T:只能选已有键[P in K]:遍历这些键T[P]:取原对象中对应属性值类型
4.3 Omit
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};
记忆拆解:
keyof T:先拿全部 keyExclude<keyof T, K>:删掉不想要的- 再映射回对象
4.4 Partial
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
核心:
? 把每个属性变可选。
4.5 Readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
核心:
readonly 给每个属性加只读。
4.6 Required
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
核心:
-? 去掉可选修饰符。
4.7 Record
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
适合:
已知 key 集合,想统一映射成同类型值。
type Roles = "admin" | "user";
type RoleMap = MyRecord<Roles, boolean>;
4.8 Exclude
type MyExclude<T, U> = T extends U ? never : T;
例子:
type A = MyExclude<"a" | "b" | "c", "a" | "b">; // "c"
本质:
条件类型 + 联合分发 + never 过滤。
4.9 Extract
type MyExtract<T, U> = T extends U ? T : never;
例子:
type A = MyExtract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
本质:
保留满足条件的那部分。
4.10 NonNullable
type MyNonNullable<T> = T extends null | undefined ? never : T;
例子:
type A = MyNonNullable<string | null | undefined>; // string
4.11 ReturnType
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never;
这是你图片里直接给出的重点之一。
记忆拆解:
- 先限制传入的是函数类型
- 用条件类型判断函数形状
- 用
infer R提取返回值
4.12 Parameters
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
作用:
提取函数参数列表元组。
4.13 ConstructorParameters
type MyConstructorParameters<T extends abstract new (...args: any[]) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
4.14 InstanceType
type MyInstanceType<T extends abstract new (...args: any[]) => any> =
T extends abstract new (...args: any[]) => infer R ? R : never;
4.15 Awaited
type MyAwaited<T> =
T extends Promise<infer R> ? MyAwaited<R> : T;
作用:
递归拆出 Promise 最终包裹的值。
4.16 工具类型实现总规律
几乎所有工具类型都能归纳为下面几种套路:
- 遍历对象:映射类型
- 过滤联合:条件类型 +
never - 提取信息:
infer - 拼接 key:模板字面量类型
- 深度处理:递归
5. 类型体操面试题
这一部分是你图片里的第三大块。不要把它当纯背题,而要把它当成“前面所有语法的综合应用”。
5.1 实现 DeepPartial
5.1.1 题目目标
把对象所有层级的属性都变成可选。
5.1.2 基础版实现
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
5.1.3 理解步骤
- 遍历
T的每个 key - 当前属性变可选
- 如果属性值还是对象,就递归处理
- 否则保留原值类型
5.1.4 注意点
简单写法里 object 会把数组、函数等也包含进去,实际项目里有时要做更细判断。
更稳一些的版本:
type DeepPartial<T> =
T extends Function ? T :
T extends Array<infer U> ? Array<DeepPartial<U>> :
T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } :
T;
5.1.5 记忆口诀
DeepPartial = Partial + 递归。
5.2 实现 DeepReadonly
5.2.1 题目目标
把对象所有层级属性都变成只读。
5.2.2 实现
type DeepReadonly<T> =
T extends Function ? T :
T extends Array<infer U> ? ReadonlyArray<DeepReadonly<U>> :
T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } :
T;
5.2.3 思路
- 函数直接返回
- 数组递归成只读数组
- 对象层层递归加
readonly - 基本类型直接返回
5.2.4 记忆口诀
DeepReadonly = Readonly + 递归。
5.3 实现 TupleToUnion
5.3.1 题目目标
把元组类型转成联合类型。
5.3.2 最简单写法
type TupleToUnion<T extends readonly any[]> = T[number];
5.3.3 为什么这么写就行
因为数组或元组的索引访问类型:
T[number]
表示“拿出这个元组所有数字索引位置的值类型”,自然就变成联合类型。
示例:
type A = TupleToUnion<["a", "b", "c"]>; // "a" | "b" | "c"
5.3.4 递归版
type TupleToUnion2<T extends any[]> =
T extends [infer F, ...infer R] ? F | TupleToUnion2<R> : never;
5.3.5 记忆口诀
元组转联合,优先想到 T[number]。
5.4 实现 Chainable
5.4.1 题目背景
这是类型体操非常经典的一题,目的是实现:
config
.option("name", "tom")
.option("age", 18)
.get();
并让最终类型变成:
{
name: string;
age: number;
}
5.4.2 实现
type Chainable<T = {}> = {
option<K extends string, V>(
key: K extends keyof T ? never : K,
value: V
): Chainable<T & { [P in K]: V }>;
get(): T;
};
5.4.3 思路拆解
- 用泛型
T保存当前累积结果 - 每次
option调用时新增一个属性 - 返回新的
Chainable<新类型> get()返回最终累积类型K extends keyof T ? never : K用来防止重复 key
5.4.4 为什么要用交叉类型
T & { [P in K]: V }
表示:
把已有配置 T 和当前新增配置合并起来。
5.4.5 记忆口诀
链式调用题的关键是:每调一次,类型就累加一次。
5.5 再补几个高频类型体操题
实现 First
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
实现 Last
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
实现 Pop
type Pop<T extends any[]> = T extends [...infer R, any] ? R : never;
实现 Shift
type Shift<T extends any[]> = T extends [any, ...infer R] ? R : never;
实现 AppendArgument
type AppendArgument<Fn, A> =
Fn extends (...args: infer P) => infer R ? (...args: [...P, A]) => R : never;
实现 Mutable
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
5.6 类型体操做题模板
看到一道题,优先按这个顺序想:
- 输入是对象、联合、函数、元组,还是字符串?
- 需要遍历吗?如果需要,先想映射类型
- 需要判断吗?如果需要,先想条件类型
- 需要提取一部分吗?如果需要,先想
infer - 需要递归吗?如果需要,注意终止条件
- 需要过滤吗?优先想到
never - 需要拼接字符串吗?优先想到模板字面量类型
6. 实战场景与思维方式
学高级类型最怕“只会题,不会用”。这一部分把类型系统放回实际开发里。
6.1 API 响应统一建模
type ApiResponse<T> = {
code: number;
message: string;
data: T;
};
type User = {
id: number;
name: string;
};
type UserResponse = ApiResponse<User>;
意义:
- 统一接口返回结构
- 每个接口只关心
data的具体类型 - 泛型真正落地
6.2 表单配置推导
type FormConfig<T> = {
[K in keyof T]: {
label: string;
value: T[K];
};
};
这样你只要定义业务对象,就能推导出对应表单结构。
6.3 事件名推导
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]?: (value: T[K]) => void;
};
这类写法在:
- 组件事件
- 状态变更监听
- 插件系统 hook 命名
里都很常见。
6.4 状态机风格的联合类型
type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string[] }
| { status: "error"; error: Error };
使用时配合缩小类型:
function render(state: RequestState) {
switch (state.status) {
case "success":
return state.data.join(",");
case "error":
return state.error.message;
default:
return state.status;
}
}
好处:
- 状态清晰
- 分支安全
- 比随便写多个可选字段更可靠
6.5 穷尽性检查
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`);
}
function render(state: RequestState) {
switch (state.status) {
case "idle":
return "idle";
case "loading":
return "loading";
case "success":
return state.data.join(",");
case "error":
return state.error.message;
default:
return assertNever(state);
}
}
意义:
如果以后新增状态但没处理,TypeScript 会在编译期提醒你。
6.6 项目里该怎么写得更实用
- 能推导就别手写重复类型
- 能约束就别用
any - 公共结构优先抽成泛型
- 联合类型比多个布尔值更清晰
- 类型体操别为了炫技,优先可读性
7. 高频面试题
7.1 什么是泛型,为什么需要泛型
参考回答:
- 泛型是把类型参数化,让函数、接口、类型别名在保持类型安全的同时具备复用能力
- 不使用泛型时,通常只能写死类型或退化成
any - 泛型的核心价值是“既灵活,又保留信息”
7.2 泛型约束和泛型默认值怎么写
参考回答:
type A<T extends { id: number }> = T;
type B<T = string> = { value: T };
说明:
extends用于约束泛型必须满足某结构=用于给泛型提供默认值
7.3 条件类型是什么
参考回答:
条件类型是类型层面的条件判断,写法为 T extends U ? X : Y。它常用于类型过滤、类型分支处理和配合 infer 做类型提取。
7.4 什么是分发条件类型
参考回答:
当条件类型左侧是裸类型参数,且传入的是联合类型时,TypeScript 会对联合中的每一项分别执行条件判断,再把结果合并成联合类型,这就是分发条件类型。
7.5 infer 的作用是什么
参考回答:
infer 只能在条件类型中使用,用来从某个复杂类型结构中提取一部分类型信息,比如函数返回值、参数列表、Promise 内部值等。
7.6 映射类型是什么
参考回答:
映射类型是通过 [K in keyof T] 遍历对象类型的所有属性,再生成一个新类型的机制,常见于 Partial、Readonly、Pick 等工具类型实现。
7.7 Pick 和 Omit 怎么实现
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};
7.8 模板字面量类型有什么用
参考回答:
模板字面量类型可以在类型层面对字符串做拼接和变换,常用于事件名推导、路由名生成、getter/setter 命名以及结合 key 重映射生成更智能的 API 类型。
7.9 DeepPartial 和 Partial 的区别
参考回答:
Partial<T> 只会把第一层属性变成可选,而 DeepPartial<T> 会递归地把所有层级属性都变成可选。
7.10 如何理解 Chainable 这类题
参考回答:
这类题的本质是“每次方法调用都返回一个携带更多信息的新泛型实例”,核心是累积类型信息,而不是只写运行时代码。
8. 5 小时学习节奏建议
按你给的学习时长,这里给你一版适合快速上手和记忆的节奏安排。
第 1 小时:先把地基搭起来
学习目标:
- 分清类型和值
- 理解
type、interface - 熟悉
keyof、typeof、in、索引访问 - 明确
extends的两种含义
必须会:
keyof TT[K]K extends keyof T
第 2 小时:高级类型主干
学习目标:
- 学会泛型
- 学会条件类型
- 学会
infer
必须会:
- 写泛型约束
- 写
If<T> - 写
ReturnType
第 3 小时:映射类型和模板字面量类型
学习目标:
- 吃透
[K in keyof T] - 理解修饰符
?、-?、readonly - 学会模板字面量类型拼接
必须会:
- 写
Partial - 写
Readonly - 写
Pick - 写事件名推导
第 4 小时:工具类型和类型体操
学习目标:
- 手写高频工具类型
- 练递归思维
- 理解类型累积
必须会:
OmitReturnTypeDeepPartialTupleToUnionChainable
第 5 小时:复盘 + 讲解 + 默写
建议:
- 不看笔记自己写 8 个工具类型
- 自己解释
extends、infer、分发条件类型 - 自己讲一遍
DeepPartial和Chainable
检验标准:
- 你能说出每一题在“遍历什么”
- 你能说出每一题在“判断什么”
- 你能说出
infer在“提取什么”
9. 一页速记总结
9.1 泛型
<T>:类型参数<T extends X>:类型约束<T = Default>:默认类型
9.2 条件类型
T extends U ? X : Y- 裸类型参数遇联合会分发
never常用于过滤
9.3 infer
- 只能在条件类型里用
- 常用于提取返回值、参数、Promise 内部值
9.4 映射类型
[K in keyof T]T[K]取值类型?、-?、readonly、-readonly控制修饰符
9.5 模板字面量类型
`prefix${T}`- 常结合
Capitalize、keyof、as使用
9.6 工具类型套路
Pick:挑 keyOmit:去 keyExclude:过滤联合Extract:提取联合ReturnType:提取返回值Parameters:提取参数
9.7 类型体操套路
- 对象题:先想映射类型
- 联合题:先想条件类型
- 函数题:先想
infer - 元组题:先想解构
infer - 深度题:先想递归
10. 背诵口诀
10.1 总口诀
泛型做复用,条件做判断,infer 做提取,映射做遍历,模板做拼接,递归做深度。
10.2 泛型口诀
尖括号里放类型,extends 加限制,等号给默认。
10.3 条件类型口诀
extends 像 if,联合会分发,never 专门过滤。
10.4 映射类型口诀
keyof 拿键,in 遍历,T[K] 取值,修饰符改属性。
10.5 infer 口诀
看见复杂结构想提取,优先想到 infer。
10.6 类型体操口诀
先看输入像什么,再看要遍历还是提取,最后考虑递归。
11. 自测题
试着不看答案,自己回答下面问题:
extends在泛型约束和条件类型里有什么区别?- 为什么
K extends keyof T能保证 key 安全? - 什么是分发条件类型,怎么阻止它?
Exclude和Extract为什么都离不开never?infer为什么只能写在条件类型里?Pick和Omit的实现核心分别是什么?Partial与DeepPartial的本质差别是什么?- 为什么
TupleToUnion<T>可以直接写成T[number]? Chainable题里为什么每次都要返回一个新的泛型类型?- 模板字面量类型为什么特别适合事件名推导?
12. 面试回答总模板
回答类型题时,推荐按这个顺序说:
- 先说它解决什么问题
- 再说核心语法结构
- 再说底层思路
- 最后举一个简单例子
例如回答 ReturnType:
- 它用来提取函数返回值类型
- 写法依赖条件类型和
infer - 核心是判断传入类型是否为函数,并从函数签名中推导出返回值
- 所以
type R = ReturnType<() => string>的结果就是string
这个模板可以套在:
- 泛型约束
- 条件类型
- 映射类型
- 模板字面量类型
- 各类工具类型
13. 最后给你的学习建议
如果你想快速学会并记住 TypeScript,不要上来就死磕特别绕的体操题。更高效的顺序是:
- 先掌握
keyof、T[K]、in - 再掌握泛型、条件类型、
infer - 再写
Pick、Omit、ReturnType - 最后再做
DeepPartial、TupleToUnion、Chainable
真正学会的标准不是“看懂答案”,而是:
- 你能自己拆出题型
- 你能自己判断该用哪种语法
- 你能自己写出第一版实现
- 你能解释为什么这样写
把 TypeScript 真正学明白的关键,不是记住多少题,而是记住这几个动作:
- 约束
- 判断
- 提取
- 遍历
- 拼接
- 递归
只要你看到题目能立刻判断“这是在做哪一种动作”,TypeScript 进阶就开始顺了。