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

前言

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

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

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

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

TypeScript = JavaScript + 静态类型检查。

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

ts 复制代码
function add(a: number, b: number): number {
  return a + b;
}

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

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

2. 基础类型

常见基础类型:

ts 复制代码
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");

数组:

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

元组:

ts 复制代码
const user: [string, number] = ["Tom", 18];

对象:

ts 复制代码
const user: { id: string; name: string } = {
  id: "001",
  name: "Tom",
};

3. anyunknownnevervoid

3.1 any

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

ts 复制代码
let value: any = "hello";

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

3.2 unknown

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

ts 复制代码
let value: unknown = "hello";

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

所以:

ts 复制代码
unknown = 类型安全版 any

3.3 void

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

ts 复制代码
function log(message: string): void {
  console.log(message);
}

3.4 never

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

ts 复制代码
function fail(message: string): never {
  throw new Error(message);
}

也常用于穷尽检查:

ts 复制代码
function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${value}`);
}

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

你可能见过这样的写法:

ts 复制代码
type Obj = {
  [props: string]: any;
};

它表示:

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

props 只是名字,可以换成 key

ts 复制代码
type Obj = {
  [key: string]: any;
};

但是项目里更推荐:

ts 复制代码
type Obj = {
  [key: string]: unknown;
};

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

ts 复制代码
const obj: Obj = {
  name: "Tom",
  age: 18,
};

const name = obj.name;

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

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

ts 复制代码
type User = {
  name: string;
  age: number;
  [key: string]: string | number;
};

5. in 的三种用法

5.1 判断属性是否存在

ts 复制代码
const user = {
  name: "Tom",
  age: 18,
};

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

5.2 联合类型收窄

ts 复制代码
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

ts 复制代码
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

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

6. extends:不只是继承

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

ts 复制代码
A extends B

意思是:

A 必须满足 B。

6.1 interface 继承

ts 复制代码
interface Person {
  name: string;
}

interface Student extends Person {
  school: string;
}

Student 同时拥有 nameschool

ts 复制代码
const s: Student = {
  name: "Tom",
  school: "No.1 School",
};

6.2 class 继承

ts 复制代码
class Animal {
  move() {
    console.log("move");
  }
}

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

6.3 泛型约束

ts 复制代码
function printName<T extends { name: string }>(obj: T) {
  console.log(obj.name);
}

意思是:

T 必须至少有 name: string

ts 复制代码
printName({ name: "Tom", age: 18 }); // OK
printName({ age: 18 }); // 报错

你也可以写得更具体:

ts 复制代码
function handle<T extends { id: string; name: string }>(item: T): T {
  return item;
}

这表示 T 至少要有 idname

ts 复制代码
const result = handle({
  id: "001",
  name: "Tom",
  age: 18,
});

result.age; // OK

泛型约束的价值是:

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

6.4 条件类型

ts 复制代码
type IsString<T> = T extends string ? true : false;

使用:

ts 复制代码
type A = IsString<string>; // true
type B = IsString<number>; // false

这里的 extends 更准确地说是:

T 是否可以赋值给 string。

例如:

ts 复制代码
type A = "hello" extends string ? true : false;
// true

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

7. T extends object

ts 复制代码
function fn<T extends object>(value: T) {
  return value;
}

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

可以:

ts 复制代码
fn({});
fn([]);
fn(() => {});
fn(new Date());

不可以:

ts 复制代码
fn("hello");
fn(123);
fn(true);

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

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

ts 复制代码
Record<string, unknown>

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

8. objectObject 的区别

8.1 object

object 表示非原始类型。

ts 复制代码
let a: object;

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

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

8.2 Object

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

ts 复制代码
let b: Object;

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

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

更推荐:

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

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

可以用联合类型:

ts 复制代码
type Primitive =
  | string
  | number
  | boolean
  | bigint
  | symbol
  | null
  | undefined;

type AnyValue = Primitive | object;

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

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

ts 复制代码
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

JSON 不包含:

ts 复制代码
undefined
function
symbol
bigint
Date
Map
Set

10. 类型守卫:value is Xxx

类型守卫用于告诉 TS:

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

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

使用:

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

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

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

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

ts 复制代码
function isString(value: string | number): value is string {
  return typeof value === "string";
}

11. 联合类型和交叉类型

11.1 联合类型 |

联合类型表示"或"。

ts 复制代码
type ID = string | number;

使用时需要收窄:

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

适合表达多种可能:

ts 复制代码
type Status = "loading" | "success" | "error";

11.2 交叉类型 &

交叉类型表示"且"。

ts 复制代码
type User = { name: string } & { age: number };

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

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

ts 复制代码
type Base = {
  id: string;
  createdAt: string;
};

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

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

ts 复制代码
type A = string & number;
// never

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

ts 复制代码
type A = { id: string } & { id: number };
// id: never

12. 函数重载

函数重载用于表达:

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

ts 复制代码
function fn(x: string): string;
function fn(x: number): number;
function fn(x: string | number): string | number {
  return x;
}

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

调用时:

ts 复制代码
const a = fn("hello"); // string
const b = fn(123); // number

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

ts 复制代码
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);
}

不允许两个参数调用:

ts 复制代码
makeDate(2026, 6); // 报错

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

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

13. 泛型

泛型可以理解成:

把类型当成参数传进去。

ts 复制代码
function identity<T>(value: T): T {
  return value;
}

调用:

ts 复制代码
const a = identity("hello"); // string
const b = identity(123); // number

泛型的价值是:

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

13.1 数组例子

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

13.2 多个泛型参数

ts 复制代码
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

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

13.3 泛型接口

ts 复制代码
type ApiResponse<T> = {
  code: number;
  message: string;
  data: T;
};

使用:

ts 复制代码
type User = {
  id: string;
  name: string;
};

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

13.4 泛型默认值

ts 复制代码
type ApiResponse<T = unknown> = {
  code: number;
  data: T;
};

14. 泛型和 keyof

这个模板非常重要:

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

使用:

ts 复制代码
const user = {
  id: 1,
  name: "Tom",
};

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

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

这里同时用到了:

ts 复制代码
T
K extends keyof T
T[K]

它表达的是:

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

15. infer

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

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

15.1 提取数组元素类型

ts 复制代码
type Item<T> = T extends Array<infer U> ? U : never;

使用:

ts 复制代码
type A = Item<string[]>; // string
type B = Item<number[]>; // number

15.2 提取 Promise 结果

ts 复制代码
type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
ts 复制代码
type A = UnwrapPromise<Promise<string>>;
// string

递归拆 Promise:

ts 复制代码
type DeepUnwrapPromise<T> = T extends Promise<infer R>
  ? DeepUnwrapPromise<R>
  : T;

15.3 提取函数返回值

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

使用:

ts 复制代码
function getUser() {
  return {
    id: 1,
    name: "Tom",
  };
}

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

15.4 提取函数参数

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

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

15.5 提取对象字段

ts 复制代码
type GetData<T> = T extends { data: infer D } ? D : never;
ts 复制代码
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> 会把对象类型的所有属性变成可选。

简化源码:

ts 复制代码
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

使用示例:

ts 复制代码
type User = {
  id: string;
  name: string;
  age: number;
};

type PartialUser = Partial<User>;

等价于:

ts 复制代码
type PartialUser = {
  id?: string;
  name?: string;
  age?: number;
};

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

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

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

16.2 Required<T>

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

简化源码:

ts 复制代码
type MyRequired<T> = {
  [K in keyof T]-?: T[K];
};

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

使用示例:

ts 复制代码
type User = {
  id?: string;
  name?: string;
};

type RequiredUser = Required<User>;

等价于:

ts 复制代码
type RequiredUser = {
  id: string;
  name: string;
};

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

ts 复制代码
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> 会把对象属性变成只读。

简化源码:

ts 复制代码
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

使用示例:

ts 复制代码
type User = {
  id: string;
  name: string;
};

type ReadonlyUser = Readonly<User>;

等价于:

ts 复制代码
type ReadonlyUser = {
  readonly id: string;
  readonly name: string;
};

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

ts 复制代码
function printUser(user: Readonly<User>) {
  console.log(user.name);

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

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

ts 复制代码
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> 从对象类型中挑选部分属性。

简化源码:

ts 复制代码
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

使用示例:

ts 复制代码
type User = {
  id: string;
  name: string;
  age: number;
  password: string;
};

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

等价于:

ts 复制代码
type UserBaseInfo = {
  id: string;
  name: string;
};

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

ts 复制代码
type UserCardProps = Pick<User, "id" | "name">;

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

16.5 Omit<T, K>

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

简化源码:

ts 复制代码
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

拆开看:

ts 复制代码
Exclude<keyof T, K>

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

使用示例:

ts 复制代码
type User = {
  id: string;
  name: string;
  password: string;
};

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

等价于:

ts 复制代码
type PublicUser = {
  id: string;
  name: string;
};

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

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

也常用于创建参数:

ts 复制代码
type CreateUserDto = Omit<User, "id">;

16.6 Record<K, T>

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

简化源码:

ts 复制代码
type MyRecord<K extends keyof any, T> = {
  [P in K]: T;
};

keyof any 等价于:

ts 复制代码
string | number | symbol

使用示例:

ts 复制代码
type Role = "admin" | "user" | "guest";

type RoleMap = Record<Role, string>;

等价于:

ts 复制代码
type RoleMap = {
  admin: string;
  user: string;
  guest: string;
};

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

ts 复制代码
type Status = "pending" | "success" | "error";

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

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

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

16.7 Exclude<T, U>

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

简化源码:

ts 复制代码
type MyExclude<T, U> = T extends U ? never : T;

使用示例:

ts 复制代码
type Status = "pending" | "success" | "error";

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

结果:

ts 复制代码
type NonErrorStatus = "pending" | "success";

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

ts 复制代码
type EventName = "click" | "hover" | "focus";

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

理解重点:

ts 复制代码
type MyExclude<T, U> = T extends U ? never : T;

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

ts 复制代码
Exclude<"a" | "b" | "c", "a">

相当于:

ts 复制代码
("a" extends "a" ? never : "a")
| ("b" extends "a" ? never : "b")
| ("c" extends "a" ? never : "c")

结果:

ts 复制代码
"b" | "c"

16.8 Extract<T, U>

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

简化源码:

ts 复制代码
type MyExtract<T, U> = T extends U ? T : never;

使用示例:

ts 复制代码
type Status = "pending" | "success" | "error";

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

结果:

ts 复制代码
type SuccessStatus = "success";

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

ts 复制代码
type FrontendEvent = "click" | "hover" | "focus";
type SupportedEvent = "click" | "focus";

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

16.9 NonNullable<T>

NonNullable<T> 从类型中排除 nullundefined

简化源码:

ts 复制代码
type MyNonNullable<T> = T extends null | undefined ? never : T;

使用示例:

ts 复制代码
type Value = string | number | null | undefined;

type SafeValue = NonNullable<Value>;

结果:

ts 复制代码
type SafeValue = string | number;

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

ts 复制代码
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> 用来获取函数返回值类型。

简化源码:

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

使用示例:

ts 复制代码
function getUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

type User = ReturnType<typeof getUser>;

结果:

ts 复制代码
type User = {
  id: string;
  name: string;
};

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

ts 复制代码
function createState() {
  return {
    count: 0,
    user: null as null | { id: string; name: string },
  };
}

type State = ReturnType<typeof createState>;

16.11 Parameters<T>

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

简化源码:

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

使用示例:

ts 复制代码
function createUser(id: string, age: number) {
  return { id, age };
}

type CreateUserParams = Parameters<typeof createUser>;

结果:

ts 复制代码
type CreateUserParams = [id: string, age: number];

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

ts 复制代码
function createUser(id: string, age: number) {
  return { id, age };
}

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

16.12 ConstructorParameters<T>

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

简化源码:

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

使用示例:

ts 复制代码
class User {
  constructor(public id: string, public name: string) {}
}

type UserConstructorParams = ConstructorParameters<typeof User>;

结果:

ts 复制代码
type UserConstructorParams = [id: string, name: string];

使用场景:工厂函数。

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

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

ts 复制代码
new (...args: any[]) => any

表示构造函数类型。

16.13 InstanceType<T>

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

简化源码:

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

使用示例:

ts 复制代码
class User {
  id = "001";
  name = "Tom";
}

type UserInstance = InstanceType<typeof User>;

结果:

ts 复制代码
type UserInstance = User;

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

ts 复制代码
class Service {
  request() {}
}

type ServiceInstance = InstanceType<typeof Service>;

const service: ServiceInstance = new Service();

16.14 Awaited<T>

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

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

ts 复制代码
type MyAwaited<T> = T extends Promise<infer R>
  ? MyAwaited<R>
  : T;

使用示例:

ts 复制代码
type A = Awaited<Promise<string>>;
// string

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

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

ts 复制代码
async function fetchUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

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

拆开看:

ts 复制代码
ReturnType<typeof fetchUser>

得到:

ts 复制代码
Promise<{ id: string; name: string }>

再用:

ts 复制代码
Awaited<...>

得到:

ts 复制代码
{ id: string; name: string }

16.15 ReadonlyArray<T>

ReadonlyArray<T> 表示只读数组。

使用示例:

ts 复制代码
const nums: ReadonlyArray<number> = [1, 2, 3];

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

也可以写成:

ts 复制代码
const nums: readonly number[] = [1, 2, 3];

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

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

16.16 ThisParameterType<T>

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

简化源码:

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

使用示例:

ts 复制代码
function fn(this: { name: string }, age: number) {
  console.log(this.name, age);
}

type ThisTypeOfFn = ThisParameterType<typeof fn>;

结果:

ts 复制代码
type ThisTypeOfFn = {
  name: string;
};

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

16.17 OmitThisParameter<T>

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

ts 复制代码
function fn(this: { name: string }, age: number) {
  console.log(this.name, age);
}

type FnWithoutThis = OmitThisParameter<typeof fn>;

结果类似:

ts 复制代码
type FnWithoutThis = (age: number) => void;

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

ts 复制代码
const bound = fn.bind({ name: "Tom" });

const run: OmitThisParameter<typeof fn> = bound;

16.18 工具类型组合使用

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

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

ts 复制代码
async function getUser() {
  return {
    id: "001",
    name: "Tom",
  };
}

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

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

ts 复制代码
type User = {
  id: string;
  name: string;
  password: string;
  createdAt: string;
};

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

结果:

ts 复制代码
type CreateUserDto = {
  name: string;
  password: string;
};

示例 3:更新接口入参。

ts 复制代码
type UpdateUserDto = Partial<Omit<User, "id" | "createdAt">>;

结果:

ts 复制代码
type UpdateUserDto = {
  name?: string;
  password?: string;
};

示例 4:状态映射。

ts 复制代码
type Status = "pending" | "success" | "error";

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

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

ts 复制代码
type Status = "pending" | "success" | "error" | "cancelled";

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

16.19 小结

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

对象属性处理:

ts 复制代码
Partial<T>
Required<T>
Readonly<T>
Pick<T, K>
Omit<T, K>
Record<K, T>

联合类型处理:

ts 复制代码
Exclude<T, U>
Extract<T, U>
NonNullable<T>

函数类型处理:

ts 复制代码
ReturnType<T>
Parameters<T>
ThisParameterType<T>
OmitThisParameter<T>

构造函数处理:

ts 复制代码
ConstructorParameters<T>
InstanceType<T>

异步类型处理:

ts 复制代码
Awaited<T>

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

ts 复制代码
keyof
in
extends
infer
映射类型
条件类型
联合类型分发

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

17. 实战建议

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

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

18. 总结

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

ts 复制代码
[key: string]: unknown

用于描述动态属性。

ts 复制代码
"name" in obj

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

ts 复制代码
T extends SomeType

用于泛型约束。

ts 复制代码
T extends U ? X : Y

用于条件类型判断。

ts 复制代码
value is SomeType

用于自定义类型守卫。

ts 复制代码
A | B

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

ts 复制代码
A & B

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

ts 复制代码
function identity<T>(value: T): T

表示泛型函数。

ts 复制代码
T extends Array<infer U> ? U : never

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

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

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

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

相关推荐
做前端的娜娜子6 分钟前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅13 分钟前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni38 分钟前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学2 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端
赵庆明老师2 小时前
Vben精讲:21-详解web-antd:tsconfig.json
前端·json·vim
wc882 小时前
微软EDGE浏览器功能学习
前端·学习·edge
破z晓2 小时前
javascript 导出excel表
开发语言·javascript·excel
Csvn3 小时前
🎯 原生 `<dialog>` 元素:终于可以扔掉一半的自定义弹窗组件了?
前端
Csvn3 小时前
🎨 CSS @layer:用「层叠优先级」终结样式打架的世纪难题
前端
小罗水4 小时前
第17章 后台管理端与演示支持
javascript·vue.js·ecmascript