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 类型系统的核心区域了。

相关推荐
罗超驿1 小时前
15.JavaScript 函数与作用域完全指南:语法、参数、表达式与作用域链实战
开发语言·前端·javascript
.千余1 小时前
【C++】C++类与对象2:C++构造函数、运算符重载与流输入输出全面解析
c语言·开发语言·前端·c++·经验分享
且听风吟_xincell1 小时前
用 TypeScript 从零写一个 TCP 聊天室(上)—— 网络编程入门实战
网络·tcp/ip·typescript
一念&2 小时前
油猴脚本教程——元数据块
javascript·浏览器·脚本·油猴
星栈2 小时前
Rust 单二进制部署,真没你想的那么“单”
前端·后端
angerdream2 小时前
Android手把手编写儿童手机远程监控App之webrtc聊天数据通道
前端
浩风祭月2 小时前
受够了每次切分支都要重装依赖:一份 Git 工作流优化指南
前端·ai编程
谭光志2 小时前
如何从零开始实现一个 AI Agent CLI
前端·javascript·ai编程
烛衔溟3 小时前
TypeScript 模块与声明文件全解
linux·ubuntu·typescript