TypeScript 进阶知识总结:从 extends、泛型到 infer,一篇打通 TS 类型系统

0 阅读8分钟

前言

TypeScript 的核心价值不是“给 JavaScript 加几个类型注释”,而是用类型系统提前描述数据结构、函数契约和业务状态。

这篇文章会从基础类型讲起,重点展开几个 TS 进阶高频点:

  • 索引签名 [key: string]: unknown
  • in 类型收窄
  • extends 的多种用法
  • value is Xxx 类型守卫
  • objectObjectunknownany 的区别
  • 联合类型和交叉类型
  • 函数重载
  • 泛型
  • infer
  • 常用工具类型源码实现和使用场景

1. TypeScript 到底解决什么问题?

TypeScript = JavaScript + 静态类型检查。

它最终还是会编译成 JavaScript,类型只存在于编译期。

function add(a: number, b: number): number {
  return a + b;
}

add(1, 2);
add("1", 2); // 报错

TS 的作用是:在代码运行前,提前发现类型错误。

2. 基础类型

常见基础类型:

let name: string = "Tom";
let age: number = 18;
let isAdmin: boolean = false;
let n: null = null;
let u: undefined = undefined;
let big: bigint = 100n;
let sym: symbol = Symbol("id");

数组:

const nums: number[] = [1, 2, 3];
const names: Array<string> = ["Tom", "Jerry"];

元组:

const user: [string, number] = ["Tom", 18];

对象:

const user: { id: string; name: string } = {
  id: "001",
  name: "Tom",
};

3. anyunknownnevervoid

3.1 any

any 表示任意类型,但会关闭类型检查。

let value: any = "hello";

value.toFixed(); // TS 不报错,但运行可能报错

3.2 unknown

unknown 也可以接收任意类型,但使用前必须判断。

let value: unknown = "hello";

if (typeof value === "string") {
  value.toUpperCase();
}

所以:

unknown = 类型安全版 any

3.3 void

void 常用于表示函数没有返回值。

function log(message: string): void {
  console.log(message);
}

3.4 never

never 表示永远不会出现的值。

function fail(message: string): never {
  throw new Error(message);
}

也常用于穷尽检查:

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

4. 索引签名:[key: string]: any

你可能见过这样的写法:

type Obj = {
  [props: string]: any;
};

它表示:

这个对象可以有任意字符串 key,并且 value 是 any 类型。

props 只是名字,可以换成 key

type Obj = {
  [key: string]: any;
};

但是项目里更推荐:

type Obj = {
  [key: string]: unknown;
};

因为 any 会放弃类型检查,而 unknown 更安全。

const obj: Obj = {
  name: "Tom",
  age: 18,
};

const name = obj.name;

if (typeof name === "string") {
  name.toUpperCase();
}

注意,一旦写了索引签名,明确属性也要兼容它:

type User = {
  name: string;
  age: number;
  [key: string]: string | number;
};

5. in 的三种用法

5.1 判断属性是否存在

const user = {
  name: "Tom",
  age: 18,
};

console.log("name" in user); // true
console.log("email" in user); // false

5.2 联合类型收窄

type Cat = {
  meow: () => void;
};

type Dog = {
  bark: () => void;
};

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

"meow" in animal 会让 TS 知道当前分支里 animalCat

5.3 映射类型中遍历 key

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

这里的 in 表示遍历 keyof T 中的每一个 key。

6. extends:不只是继承

extends 是 TS 中非常重要的关键字。核心可以理解成:

A extends B

意思是:

A 必须满足 B。

6.1 interface 继承

interface Person {
  name: string;
}

interface Student extends Person {
  school: string;
}

Student 同时拥有 nameschool

const s: Student = {
  name: "Tom",
  school: "No.1 School",
};

6.2 class 继承

class Animal {
  move() {
    console.log("move");
  }
}

class Dog extends Animal {
  bark() {
    console.log("bark");
  }
}

6.3 泛型约束

function printName<T extends { name: string }>(obj: T) {
  console.log(obj.name);
}

意思是:

T 必须至少有 name: string

printName({ name: "Tom", age: 18 }); // OK
printName({ age: 18 }); // 报错

你也可以写得更具体:

function handle<T extends { id: string; name: string }>(item: T): T {
  return item;
}

这表示 T 至少要有 idname

const result = handle({
  id: "001",
  name: "Tom",
  age: 18,
});

result.age; // OK

泛型约束的价值是:

既限制结构,又保留传入对象的完整类型。

6.4 条件类型

type IsString<T> = T extends string ? true : false;

使用:

type A = IsString<string>; // true
type B = IsString<number>; // false

这里的 extends 更准确地说是:

T 是否可以赋值给 string。

例如:

type A = "hello" extends string ? true : false;
// true

type B = string extends "hello" ? true : false;
// false

7. T extends object

function fn<T extends object>(value: T) {
  return value;
}

T extends object 表示 T 必须是非原始类型。

可以:

fn({});
fn([]);
fn(() => {});
fn(new Date());

不可以:

fn("hello");
fn(123);
fn(true);

注意:数组和函数也属于 object

如果你想表达普通键值对象,可以考虑:

Record<string, unknown>

但它和普通对象也不是完全等价,实际项目里要看具体场景。

8. objectObject 的区别

8.1 object

object 表示非原始类型。

let a: object;

a = {};
a = [];
a = () => {};

a = "hello"; // 报错
a = 123; // 报错

8.2 Object

Object 范围更宽,几乎表示所有非 null、非 undefined 的值。

let b: Object;

b = {};
b = [];
b = "hello";
b = 123;
b = true;

所以真实项目里不建议用大写 Object 表示普通对象。

更推荐:

unknown // 任意类型,安全
object // 非原始类型
Record<string, unknown> // 普通键值对象

9. 不用 any / unknown,如何表示所有 JS 值?

可以用联合类型:

type Primitive =
  | string
  | number
  | boolean
  | bigint
  | symbol
  | null
  | undefined;

type AnyValue = Primitive | object;

因为 object 包含普通对象、数组、函数、Date、Map、Set 等非原始值。

如果你要表示 JSON 值,可以这样写:

type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

JSON 不包含:

undefined
function
symbol
bigint
Date
Map
Set

10. 类型守卫:value is Xxx

类型守卫用于告诉 TS:

如果这个函数返回 true,那么参数就是某个类型。

function isPlainObject(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

使用:

function handle(value: unknown) {
  if (isPlainObject(value)) {
    value.name; // OK,类型是 unknown
  }
}

属性值仍然是 unknown,所以要继续判断:

function handle(value: unknown) {
  if (isPlainObject(value) && typeof value.name === "string") {
    value.name.toUpperCase();
  }
}

类型守卫不必须和 unknown 配合,也可以用于联合类型:

function isString(value: string | number): value is string {
  return typeof value === "string";
}

11. 联合类型和交叉类型

11.1 联合类型 |

联合类型表示“或”。

type ID = string | number;

使用时需要收窄:

function printId(id: string | number) {
  if (typeof id === "string") {
    id.toUpperCase();
  } else {
    id.toFixed(2);
  }
}

适合表达多种可能:

type Status = "loading" | "success" | "error";

11.2 交叉类型 &

交叉类型表示“且”。

type User = { name: string } & { age: number };

const user: User = {
  name: "Tom",
  age: 18,
};

交叉类型常用于合并对象结构:

type Base = {
  id: string;
  createdAt: string;
};

type User = Base & {
  name: string;
};

注意基础类型交叉通常会得到 never

type A = string & number;
// never

对象属性冲突也会导致不可能类型:

type A = { id: string } & { id: number };
// id: never

12. 函数重载

函数重载用于表达:

同一个函数,不同参数类型或参数数量,对应不同返回类型。

function fn(x: string): string;
function fn(x: number): number;
function fn(x: string | number): string | number {
  return x;
}

前两行是重载签名,最后一行是实现签名。

调用时:

const a = fn("hello"); // string
const b = fn(123); // number

参数数量不同也可以重载:

function makeDate(timestamp: number): Date;
function makeDate(year: number, month: number, day: number): Date;

function makeDate(
  yearOrTimestamp: number,
  month?: number,
  day?: number
): Date {
  if (month !== undefined && day !== undefined) {
    return new Date(yearOrTimestamp, month, day);
  }

  return new Date(yearOrTimestamp);
}

不允许两个参数调用:

makeDate(2026, 6); // 报错

如果返回值不随参数类型变化,优先用联合类型。

如果输入和输出保持同一种类型关系,优先用泛型。

13. 泛型

泛型可以理解成:

把类型当成参数传进去。

function identity<T>(value: T): T {
  return value;
}

调用:

const a = identity("hello"); // string
const b = identity(123); // number

泛型的价值是:

既能复用逻辑,又能保留具体类型。

13.1 数组例子

function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
const a = first([1, 2, 3]); // number | undefined
const b = first(["a", "b"]); // string | undefined

13.2 多个泛型参数

function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

const result = pair("age", 18);
// [string, number]

13.3 泛型接口

type ApiResponse<T> = {
  code: number;
  message: string;
  data: T;
};

使用:

type User = {
  id: string;
  name: string;
};

const res: ApiResponse<User> = {
  code: 0,
  message: "ok",
  data: {
    id: "001",
    name: "Tom",
  },
};

13.4 泛型默认值

type ApiResponse<T = unknown> = {
  code: number;
  data: T;
};

14. 泛型和 keyof

这个模板非常重要:

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

使用:

const user = {
  id: 1,
  name: "Tom",
};

const id = getValue(user, "id"); // number
const name = getValue(user, "name"); // string

getValue(user, "age"); // 报错

这里同时用到了:

T
K extends keyof T
T[K]

它表达的是:

key 必须是 obj 的 key,返回值类型就是这个 key 对应的 value 类型。

15. infer

infer 用于条件类型中,作用是:

从一个类型结构中提取某一部分类型。

15.1 提取数组元素类型

type Item<T> = T extends Array<infer U> ? U : never;

使用:

type A = Item<string[]>; // string
type B = Item<number[]>; // number

15.2 提取 Promise 结果

type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
type A = UnwrapPromise<Promise<string>>;
// string

递归拆 Promise:

type DeepUnwrapPromise<T> = T extends Promise<infer R>
  ? DeepUnwrapPromise<R>
  : T;

15.3 提取函数返回值

type MyReturnType<T> =
  T extends (...args: any[]) => infer R ? R : never;

使用:

function getUser() {
  return {
    id: 1,
    name: "Tom",
  };
}

type User = MyReturnType<typeof getUser>;
// { id: number; name: string }

15.4 提取函数参数

type MyParameters<T> =
  T extends (...args: infer P) => any ? P : never;
function createUser(id: string, age: number) {}

type Params = MyParameters<typeof createUser>;
// [id: string, age: number]

15.5 提取对象字段

type GetData<T> = T extends { data: infer D } ? D : never;
type Response = {
  code: number;
  data: {
    id: string;
    name: string;
  };
};

type Data = GetData<Response>;
// { id: string; name: string }

16. TS 里的常用工具类型:源码实现 + 使用场景

TypeScript 内置了很多工具类型,它们本质上都是基于这些能力组合出来的:

  • 泛型
  • keyof
  • 映射类型 [K in keyof T]
  • 条件类型 T extends U ? X : Y
  • infer
  • 联合类型分发

下面逐个看常用工具类型。

16.1 Partial<T>

Partial<T> 会把对象类型的所有属性变成可选。

简化源码:

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

使用示例:

type User = {
  id: string;
  name: string;
  age: number;
};

type PartialUser = Partial<User>;

等价于:

type PartialUser = {
  id?: string;
  name?: string;
  age?: number;
};

使用场景:更新部分字段。

function updateUser(id: string, patch: Partial<User>) {
  // 只更新传入的字段
}

updateUser("001", {
  name: "Jerry",
});

16.2 Required<T>

Required<T> 会把对象类型的所有属性变成必选。

简化源码:

type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

这里的 -? 表示移除可选标记。

使用示例:

type User = {
  id?: string;
  name?: string;
};

type RequiredUser = Required<User>;

等价于:

type RequiredUser = {
  id: string;
  name: string;
};

使用场景:配置合并后,内部使用完整配置。

type Config = {
  host?: string;
  port?: number;
};

const defaultConfig: Required<Config> = {
  host: "localhost",
  port: 3000,
};

function createServer(config: Config) {
  const finalConfig: Required<Config> = {
    ...defaultConfig,
    ...config,
  };

  finalConfig.host;
  finalConfig.port;
}

16.3 Readonly<T>

Readonly<T> 会把对象属性变成只读。

简化源码:

type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

使用示例:

type User = {
  id: string;
  name: string;
};

type ReadonlyUser = Readonly<User>;

等价于:

type ReadonlyUser = {
  readonly id: string;
  readonly name: string;
};

使用场景:防止函数内部修改传入对象。

function printUser(user: Readonly<User>) {
  console.log(user.name);

  user.name = "Jerry"; // 报错
}

注意:Readonly<T> 默认是浅只读。

type User = {
  profile: {
    age: number;
  };
};

const user: Readonly<User> = {
  profile: {
    age: 18,
  },
};

user.profile.age = 20; // 可以,因为 profile 内部不是 readonly

16.4 Pick<T, K>

Pick<T, K> 从对象类型中挑选部分属性。

简化源码:

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

使用示例:

type User = {
  id: string;
  name: string;
  age: number;
  password: string;
};

type UserBaseInfo = Pick<User, "id" | "name">;

等价于:

type UserBaseInfo = {
  id: string;
  name: string;
};

使用场景:接口返回、组件 props、表格字段。

type UserCardProps = Pick<User, "id" | "name">;

function renderUserCard(user: UserCardProps) {
  return `${user.id} - ${user.name}`;
}

16.5 Omit<T, K>

Omit<T, K> 从对象类型中排除部分属性。

简化源码:

type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

拆开看:

Exclude<keyof T, K>

先从所有 key 里排除 K,再用 Pick 取剩下的字段。

使用示例:

type User = {
  id: string;
  name: string;
  password: string;
};

type PublicUser = Omit<User, "password">;

等价于:

type PublicUser = {
  id: string;
  name: string;
};

使用场景:隐藏敏感字段。

function toPublicUser(user: User): Omit<User, "password"> {
  const { password, ...rest } = user;
  return rest;
}

也常用于创建参数:

type CreateUserDto = Omit<User, "id">;

16.6 Record<K, T>

Record<K, T> 用来创建一个 key/value 对象类型。

简化源码:

type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

keyof any 等价于:

string | number | symbol

使用示例:

type Role = "admin" | "user" | "guest";

type RoleMap = Record<Role, string>;

等价于:

type RoleMap = {
  admin: string;
  user: string;
  guest: string;
};

使用场景:枚举映射、状态映射、字典对象。

type Status = "pending" | "success" | "error";

const statusText: Record<Status, string> = {
  pending: "处理中",
  success: "成功",
  error: "失败",
};

这样如果少写一个 key,TS 会报错:

const statusText: Record<Status, string> = {
  pending: "处理中",
  success: "成功",
  // error 缺失,报错
};

16.7 Exclude<T, U>

Exclude<T, U> 从联合类型 T 中排除可以赋值给 U 的类型。

简化源码:

type MyExclude<T, U> = T extends U ? never : T;

使用示例:

type Status = "pending" | "success" | "error";

type NonErrorStatus = Exclude<Status, "error">;

结果:

type NonErrorStatus = "pending" | "success";

使用场景:从联合类型里排除某些值。

type EventName = "click" | "hover" | "focus";

type MouseEventName = Exclude<EventName, "focus">;
// "click" | "hover"

理解重点:

type MyExclude<T, U> = T extends U ? never : T;

T 是联合类型时,条件类型会自动分发:

Exclude<"a" | "b" | "c", "a">

相当于:

("a" extends "a" ? never : "a")
| ("b" extends "a" ? never : "b")
| ("c" extends "a" ? never : "c")

结果:

"b" | "c"

16.8 Extract<T, U>

Extract<T, U> 从联合类型 T 中提取可以赋值给 U 的类型。

简化源码:

type MyExtract<T, U> = T extends U ? T : never;

使用示例:

type Status = "pending" | "success" | "error";

type SuccessStatus = Extract<Status, "success" | "done">;

结果:

type SuccessStatus = "success";

使用场景:从联合类型中取交集。

type FrontendEvent = "click" | "hover" | "focus";
type SupportedEvent = "click" | "focus";

type AvailableEvent = Extract<FrontendEvent, SupportedEvent>;
// "click" | "focus"

16.9 NonNullable<T>

NonNullable<T> 从类型中排除 nullundefined

简化源码:

type MyNonNullable<T> = T extends null | undefined ? never : T;

使用示例:

type Value = string | number | null | undefined;

type SafeValue = NonNullable<Value>;

结果:

type SafeValue = string | number;

使用场景:处理已经判空后的类型。

function assertValue<T>(value: T): NonNullable<T> {
  if (value === null || value === undefined) {
    throw new Error("value is empty");
  }

  return value;
}

16.10 ReturnType<T>

ReturnType<T> 用来获取函数返回值类型。

简化源码:

type MyReturnType<T extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R ? R : never;

使用示例:

function getUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

type User = ReturnType<typeof getUser>;

结果:

type User = {
  id: string;
  name: string;
};

使用场景:复用函数返回值类型,避免重复声明。

function createState() {
  return {
    count: 0,
    user: null as null | { id: string; name: string },
  };
}

type State = ReturnType<typeof createState>;

16.11 Parameters<T>

Parameters<T> 用来获取函数参数类型,结果是一个元组。

简化源码:

type MyParameters<T extends (...args: any[]) => any> =
  T extends (...args: infer P) => any ? P : never;

使用示例:

function createUser(id: string, age: number) {
  return { id, age };
}

type CreateUserParams = Parameters<typeof createUser>;

结果:

type CreateUserParams = [id: string, age: number];

使用场景:封装函数、转发参数。

function createUser(id: string, age: number) {
  return { id, age };
}

function wrapper(...args: Parameters<typeof createUser>) {
  return createUser(...args);
}

16.12 ConstructorParameters<T>

ConstructorParameters<T> 用来获取构造函数参数类型。

简化源码:

type MyConstructorParameters<T extends abstract new (...args: any[]) => any> =
  T extends abstract new (...args: infer P) => any ? P : never;

使用示例:

class User {
  constructor(public id: string, public name: string) {}
}

type UserConstructorParams = ConstructorParameters<typeof User>;

结果:

type UserConstructorParams = [id: string, name: string];

使用场景:工厂函数。

function createInstance<T extends abstract new (...args: any[]) => any>(
  Ctor: T,
  ...args: ConstructorParameters<T>
): InstanceType<T> {
  return new Ctor(...args);
}

如果遇到 abstract new 不好理解,可以先记:

new (...args: any[]) => any

表示构造函数类型。

16.13 InstanceType<T>

InstanceType<T> 用来获取构造函数创建出来的实例类型。

简化源码:

type MyInstanceType<T extends abstract new (...args: any[]) => any> =
  T extends abstract new (...args: any[]) => infer R ? R : never;

使用示例:

class User {
  id = "001";
  name = "Tom";
}

type UserInstance = InstanceType<typeof User>;

结果:

type UserInstance = User;

使用场景:根据 class 自动得到实例类型。

class Service {
  request() {}
}

type ServiceInstance = InstanceType<typeof Service>;

const service: ServiceInstance = new Service();

16.14 Awaited<T>

Awaited<T> 用来获取 Promise 最终 resolve 出来的类型。

真实源码更复杂,这里写一个简化版本:

type MyAwaited<T> = T extends Promise<infer R>
  ? MyAwaited<R>
  : T;

使用示例:

type A = Awaited<Promise<string>>;
// string

type B = Awaited<Promise<Promise<number>>>;
// number

使用场景:提取异步函数返回数据类型。

async function fetchUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

type User = Awaited<ReturnType<typeof fetchUser>>;

拆开看:

ReturnType<typeof fetchUser>

得到:

Promise<{ id: string; name: string }>

再用:

Awaited<...>

得到:

{ id: string; name: string }

16.15 ReadonlyArray<T>

ReadonlyArray<T> 表示只读数组。

使用示例:

const nums: ReadonlyArray<number> = [1, 2, 3];

nums.push(4); // 报错
nums[0] = 10; // 报错

也可以写成:

const nums: readonly number[] = [1, 2, 3];

使用场景:函数不应该修改传入数组。

function sum(nums: readonly number[]) {
  return nums.reduce((total, item) => total + item, 0);
}

16.16 ThisParameterType<T>

ThisParameterType<T> 用来提取函数里的 this 参数类型。

简化源码:

type MyThisParameterType<T> =
  T extends (this: infer U, ...args: any[]) => any ? U : unknown;

使用示例:

function fn(this: { name: string }, age: number) {
  console.log(this.name, age);
}

type ThisTypeOfFn = ThisParameterType<typeof fn>;

结果:

type ThisTypeOfFn = {
  name: string;
};

使用场景:处理依赖 this 的老代码或库封装。

16.17 OmitThisParameter<T>

OmitThisParameter<T> 用来移除函数里的 this 参数。

function fn(this: { name: string }, age: number) {
  console.log(this.name, age);
}

type FnWithoutThis = OmitThisParameter<typeof fn>;

结果类似:

type FnWithoutThis = (age: number) => void;

使用场景:把依赖 this 的函数 bind 之后再使用。

const bound = fn.bind({ name: "Tom" });

const run: OmitThisParameter<typeof fn> = bound;

16.18 工具类型组合使用

工具类型真正强大的地方在于组合。

示例 1:提取异步函数返回数据。

async function getUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

type User = Awaited<ReturnType<typeof getUser>>;

示例 2:创建接口入参类型。

type User = {
  id: string;
  name: string;
  password: string;
  createdAt: string;
};

type CreateUserDto = Omit<User, "id" | "createdAt">;

结果:

type CreateUserDto = {
  name: string;
  password: string;
};

示例 3:更新接口入参。

type UpdateUserDto = Partial<Omit<User, "id" | "createdAt">>;

结果:

type UpdateUserDto = {
  name?: string;
  password?: string;
};

示例 4:状态映射。

type Status = "pending" | "success" | "error";

const statusText: Record<Status, string> = {
  pending: "处理中",
  success: "成功",
  error: "失败",
};

示例 5:从联合类型中排除某些状态。

type Status = "pending" | "success" | "error" | "cancelled";

type ActiveStatus = Exclude<Status, "cancelled">;

16.19 小结

常用工具类型可以按用途分类记忆。

对象属性处理:

Partial<T>
Required<T>
Readonly<T>
Pick<T, K>
Omit<T, K>
Record<K, T>

联合类型处理:

Exclude<T, U>
Extract<T, U>
NonNullable<T>

函数类型处理:

ReturnType<T>
Parameters<T>
ThisParameterType<T>
OmitThisParameter<T>

构造函数处理:

ConstructorParameters<T>
InstanceType<T>

异步类型处理:

Awaited<T>

它们背后的核心能力其实就几个:

keyof
in
extends
infer
映射类型
条件类型
联合类型分发

掌握这些底层能力后,工具类型就不是黑盒了。

17. 实战建议

写 TS 时可以记住这些习惯:

  • 不确定类型时,优先用 unknown,少用 any
  • 对外部数据,比如接口返回值,不要盲信类型。
  • 描述动态对象时,优先考虑 [key: string]: unknown
  • 对普通对象可以用 Record<string, unknown>,但要理解它不是所有对象。
  • 表达多种状态时,用联合类型。
  • 表达对象合并时,用交叉类型。
  • 参数和返回值有类型关系时,用泛型。
  • 不同参数组合对应不同返回类型时,用函数重载。
  • 从复杂类型里提取子类型时,用 infer
  • extends 不只表示继承,更常用于泛型约束和条件类型判断。

18. 总结

这篇文章里,我们围绕 TS 类型系统梳理了这些重点:

[key: string]: unknown

用于描述动态属性。

"name" in obj

用于属性判断和类型收窄。

T extends SomeType

用于泛型约束。

T extends U ? X : Y

用于条件类型判断。

value is SomeType

用于自定义类型守卫。

A | B

表示联合类型,满足其中一种。

A & B

表示交叉类型,同时满足多种。

function identity<T>(value: T): T

表示泛型函数。

T extends Array<infer U> ? U : never

表示从数组中提取元素类型。

TypeScript 的难点不在于语法多,而在于要理解这些语法背后的共同目标:

用类型系统描述真实业务里的数据关系,让错误尽量在编译期暴露出来。

当你能熟练使用 extendskeyof、泛型、类型守卫、infer 这些工具时,就已经进入 TS 类型系统的核心区域了。