TypeScript学习系列-十二种工具类型

19 阅读19分钟

Hello~大家好。我是秋天的一阵风

🎉 欢迎来到 TypeScript 深度探索系列专栏!在这里,我们将深入探索 TypeScript 的各种奥秘,从基础到高级,一步步揭开它的神秘面纱。📚

🚀 本篇文章是该系列的一部分,专注于探究 TypeScript 的十二种工具类型。这些工具类型是 TypeScript 提供的强大工具,能够帮助你在开发中实现更灵活、更强大的类型操作。

🎉 通过本文的介绍,你将能够更好地利用 TypeScript 的工具类型,提升你的类型操作能力,编写出更健壮、更> 灵活的代码。让我们一起深入探索这些强大的工具类型吧!🔍

一、Required:让可选属性变得“必填”

Required的作用是将一个类型中的所有属性都变为必选的。想象一下,你有一个接口,其中有些属性是可选的,但在某些情况下,你希望所有的属性都必须被明确地提供,这时候Required就派上用场了。例如,我们有一个User接口,其中email属性是可选的:

interface User {
    id: number;
    name: string;
    email?: string;
}

如果我们想要一个所有属性都必须提供的User类型,就可以使用Required

type RequiredUser = Required<User>;

这样,RequiredUser类型中的email属性就从可选变成了必选。

使用场景

1. 确保用户输入的完整性

在处理用户输入时,可能需要确保用户提供了所有必要的信息。Required<T> 可以帮助你确保用户输入的完整性。

type UserInput = {
  name?: string;
  email?: string;
  phone?: string;
};

function processInput(input: Required<UserInput>) {
  console.log(input.name, input.email, input.phone);
}

// 处理用户输入时必须传入所有属性
processInput({ name: "John", email: "john@example.com", phone: "123-456-7890" });
// processInput({ name: "John" }); // Error: Property 'email' is missing in type '{ name: string; }' but required in type 'Required<UserInput>'.

2. 确保表单数据的完整性

在处理表单数据时,可能需要确保用户填写了所有必要的字段。Required<T> 可以帮助你确保表单数据的完整性。

type FormData = {
  username?: string;
  password?: string;
  confirmPassword?: string;
};

function validateForm(data: Required<FormData>) {
  if (data.password !== data.confirmPassword) {
    throw new Error("Passwords do not match");
  }
  console.log(data.username, data.password);
}

// 调用时必须传入所有属性
validateForm({
  username: "john_doe",
  password: "123456",
  confirmPassword: "123456",
});
// validateForm({ username: "john_doe" }); // Error: Property 'password' is missing in type '{ username: string; }' but required in type 'Required<FormData>'.

 3. 确保 API 请求参数的完整性

在调用外部 API 时,通常需要确保请求参数是完整的。Required<T> 可以帮助你确保所有必要的参数都被传入。

type RequestOptions = {
  method?: string;
  url?: string;
  headers?: { [key: string]: string };
  body?: any;
};

function makeRequest(options: Required<RequestOptions>) {
  console.log(options.method, options.url, options.headers, options.body);
}

// 调用时必须传入所有属性
makeRequest({
  method: "GET",
  url: "https://api.example.com/data",
  headers: { "Content-Type": "application/json" },
  body: null,
});
// makeRequest({ method: "GET" }); // Error: Property 'url' is missing in type '{ method: string; }' but required in type 'Required<RequestOptions>'.

Required的实现原理

Required的实现原理与Partial类似,但它将所有属性都标记为必选。它的定义如下:

type Required<T> = {
    [P in keyof T]-?: T[P];
};

这里的关键是-?,它表示移除属性的可选性,使得所有属性都必须被明确地提供。

二、Partial:让必选属性变得“可选”

Required相反,Partial的作用是将一个类型中的所有属性都变为可选的。这在某些场景下非常有用,比如当你只需要部分属性时,或者在构建一个对象时,某些属性可能暂时没有值。继续使用上面的User接口:

interface User {
    id: number;
    name: string;
    email?: string;
}

type PartialUser = Partial<User>;

现在,PartialUser类型中的idnameemail属性都可以选择性地提供。

使用场景

1. 处理部分表单数据

在处理表单时,可能只需要部分字段,而不是整个表单数据。Partial<T> 可以帮助你定义这些部分字段。

type FormData = {
  username: string;
  password: string;
  confirmPassword: string;
};

function updateForm(data: Partial<FormData>) {
  console.log(data);
}

// 调用时可以只传入部分字段
updateForm({ username: "john_doe" });
updateForm({ password: "123456", confirmPassword: "123456" });

这样,我们在处理表单数据时,就可以灵活地处理用户输入的部分数据。

2. 处理部分 API 请求参数

在调用外部 API 时,可能只需要部分请求参数,而不是所有参数。Partial<T> 可以帮助你定义这些部分参数。

type RequestOptions = {
  method: string;
  url: string;
  headers: { [key: string]: string };
  body: any;
};

function makePartialRequest(options: Partial<RequestOptions>) {
  console.log(options);
}

// 调用部分请求参数时可以只传入部分字段
makePartialRequest({ method: "GET", url: "https://api.example.com/data" });
makePartialRequest({ headers: { "Content-Type": "application/json" }, body: { key: "value" } });

Partial的实现原理

Partial的实现原理基于TypeScript的映射类型。它通过遍历一个类型的所有属性,并将每个属性都标记为可选,来生成一个新的类型。具体来说,Partial的定义如下:

type Partial<T> = {
    [P in keyof T]?: T[P];
};

这里,keyof T获取类型T的所有属性名的联合类型,然后通过[P in keyof T]遍历这些属性名,并在每个属性后面加上?,表示该属性是可选的。

三、Exclude:从联合类型中排除特定类型

Exclude 的作用是从一个联合类型中排除特定的类型,生成一个新的联合类型。这在某些情况下非常有用,比如当你需要从一个联合类型中移除某些不希望出现的类型时,Exclude 就可以派上用场了。例如,我们有一个联合类型 Value

type Value = string | number | null | undefined;

如果我们需要一个不包含 nullundefinedValue 类型,就可以使用 Exclude

type NonNullableValue = Exclude<Value, null | undefined>;

这样,NonNullableValue 类型就只包含 stringnumber

使用场景

1. 过滤函数参数类型

在定义函数时,可能需要确保某些参数不包含特定的类型。Exclude<T, U> 可以帮助你实现这一点。

type Value = string | number | null | undefined;

function processValue(value: Exclude<Value, null | undefined>) {
    console.log(value);
}

processValue("hello"); // "hello"
processValue(123); // 123
// processValue(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.
// processValue(undefined); // Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.

2. 过滤数组元素类型

在处理数组时,可能需要确保数组中的某些元素不包含特定的类型。Exclude<T, U> 可以帮助你实现这一点。

type Value = string | number | null | undefined;

function filterValues(values: Value[]): Exclude<Value, null | undefined>[] {
    return values.filter(value => value !== null && value !== undefined) as Exclude<Value, null | undefined>[];
}

const values: Value[] = ["hello", 123, null, undefined];
const filteredValues = filterValues(values);
console.log(filteredValues); // ["hello", 123]

3. 过滤事件处理函数的参数类型

在定义事件处理函数时,可能需要确保某些参数不包含特定的类型。Exclude<T, U> 可以帮助你实现这一点。

type EventData = { type: 'click'; x: number; y: number } | { type: 'mouseover'; clientX: number; clientY: number } | null;

function handleEvent(event: Exclude<EventData, null>) {
    console.log(event.type, event.x, event.y);
}

const event: EventData = { type: 'click', x: 100, y: 200 };
handleEvent(event); // "click" 100 200
// handleEvent(null); // Error: Argument of type 'null' is not assignable to parameter of type '{ type: "click"; x: number; y: number; } | { type: "mouseover"; clientX: number; clientY: number; }'.

Exclude 的实现原理

Exclude 的实现原理是通过条件类型来排除特定的类型。它的定义如下:

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

这里的关键是 T extends U ? never : T,它表示如果 TU 的子类型,则返回 never,否则返回 T。这种方式使得 Exclude 能够从联合类型中移除特定的类型。

四、Extract:从联合类型中提取特定类型

Extract 的作用是从一个联合类型中提取特定的类型,生成一个新的联合类型。这在某些情况下非常有用,比如当你需要从一个联合类型中提取某些特定的类型时,Extract 就可以派上用场了。例如,我们有一个联合类型 Value

type Value = string | number | null | undefined;

如果我们需要一个只包含 stringnumberValue 类型,就可以使用 Extract

type ExtractedValue = Extract<Value, string | number>;

这样,ExtractedValue 类型就只包含 stringnumber

使用场景

1. 提取特定类型的函数参数

在定义函数时,可能需要确保某些参数只包含特定的类型。Extract<T, U> 可以帮助你实现这一点。

type Value = string | number | null | undefined;

function processValue(value: Extract<Value, string | number>) {
    console.log(value);
}

processValue("hello"); // "hello"
processValue(123); // 123
// processValue(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.
// processValue(undefined); // Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.

2. 提取特定类型的数组元素

在处理数组时,可能需要确保数组中的某些元素只包含特定的类型。Extract<T, U> 可以帮助你实现这一点。

type Value = string | number | null | undefined;

function filterValues(values: Value[]): Extract<Value, string | number>[] {
    return values.filter(value => typeof value === 'string' || typeof value === 'number') as Extract<Value, string | number>[];
}

const values: Value[] = ["hello", 123, null, undefined];
const filteredValues = filterValues(values);
console.log(filteredValues); // ["hello", 123]

3. 提取特定类型的事件处理函数的参数

在定义事件处理函数时,可能需要确保某些参数只包含特定的类型。Extract<T, U> 可以帮助你实现这一点。

type EventData = { type: 'click'; x: number; y: number } | { type: 'mouseover'; clientX: number; clientY: number } | null;

function handleEvent(event: Extract<EventData, { type: 'click'; x: number; y: number }>) {
    console.log(event.type, event.x, event.y);
}

const event: EventData = { type: 'click', x: 100, y: 200 };
handleEvent(event); // "click" 100 200
// handleEvent({ type: 'mouseover', clientX: 100, clientY: 200 }); // Error: Argument of type '{ type: "mouseover"; clientX: number; clientY: number; }' is not assignable to parameter of type '{ type: "click"; x: number; y: number; }'.

Extract 的实现原理

Extract 的实现原理是通过条件类型来提取特定的类型。它的定义如下:

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

这里的关键是 T extends U ? T : never,它表示如果 TU 的子类型,则返回 T,否则返回 never。这种方式使得 Extract 能够从联合类型中提取特定的类型。

五、ReadOnly:让属性变得“只读”

ReadOnly 的作用是将一个类型中的所有属性都变为只读的。这意味着你不能修改这些属性的值,只能读取它们。这在某些情况下非常有用,比如当你希望某个对象在初始化后不能被修改时,ReadOnly 就可以派上用场了。例如,我们有一个 User 接口:

interface User {
    id: number;
    name: string;
    email: string;
}

如果我们想要一个所有属性都只读的 User 类型,就可以使用 ReadOnly

type ReadOnlyUser = Readonly<User>;

使用场景

1. 确保配置对象不可变

在加载配置文件时,可能希望配置对象在初始化后不能被修改。Readonly<T> 可以帮助你实现这一点。

type ConfigFile = {
    database: {
        host: string;
        port: number;
    };
    server: {
        host: string;
        port: number;
    };
};

function loadConfig(config: Readonly<ConfigFile>) {
    console.log(config.database.host, config.database.port, config.server.host, config.server.port);
}

// 加载配置文件时可以传入一个只读对象
const config: ConfigFile = {
    database: { host: "localhost", port: 5432 },
    server: { host: "localhost", port: 8080 },
};

loadConfig(config);
// config.database.host = "127.0.0.1"; // Error: Cannot assign to 'host' because it is a read-only property.

ReadOnly 的实现原理

Readonly 的实现原理是通过将所有属性标记为只读来实现的。它的定义如下:

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

六、Record:创建属性键到值的映射

Record 的作用是创建一个类型,其属性键来自一个联合类型,而属性值来自另一个类型。这在某些情况下非常有用,比如当你需要创建一个对象,其键是某种特定的字符串或数字,而值是某种特定的类型时,Record 就可以派上用场了。例如,我们有一个联合类型 Keys 和一个值类型 Value

type Keys = 'a' | 'b' | 'c';
type Value = number;

type MyRecord = Record<Keys, Value>;

这样,MyRecord 类型就是一个对象,其键是 'a''b''c',而值是 number 类型。

使用场景

1. 创建事件处理映射

在事件处理中,可能需要一个对象,其键是事件类型,而值是某种特定的事件处理函数。Record<K, T> 可以帮助你实现这一点。

type EventTypes = 'click' | 'mouseover' | 'mouseout';
type EventHandler = (event: Event) => void;

function createEventMap(events: Record<EventTypes, EventHandler>) {
    console.log(events.click, events.mouseover, events.mouseout);
}

// 创建事件处理映射时可以传入一个对象,其键是 EventTypes 中的值,而值是 EventHandler 类型
createEventMap({
    click: (event) => console.log('Clicked', event),
    mouseover: (event) => console.log('Mouseover', event),
    mouseout: (event) => console.log('Mouseout', event),
});

2. 创建国际化(i18n)映射

在国际化(i18n)中,可能需要一个对象,其键是语言代码,而值是某种特定的翻译对象。Record<K, T> 可以帮助你实现这一点。

type Locale = 'en' | 'fr' | 'es';
type Translation = { [key: string]: string };

function createTranslationMap(translations: Record<Locale, Translation>) {
    console.log(translations.en, translations.fr, translations.es);
}

// 创建翻译映射时可以传入一个对象,其键是 Locale 中的值,而值是 Translation 类型
createTranslationMap({
    en: { greeting: 'Hello', farewell: 'Goodbye' },
    fr: { greeting: 'Bonjour', farewell: 'Au revoir' },
    es: { greeting: 'Hola', farewell: 'Adiós' },
});

Record 的实现原理

Record 的实现原理是通过将一个联合类型中的每个值作为属性键,将另一个类型作为属性值来创建一个新的类型。它的定义如下:

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

这里的关键是 [P in K],它表示遍历联合类型 K 中的每个值,并将其作为属性键,而 T 则是属性值的类型。

七、Pick:选择性地提取属性

Pick 的作用是从一个类型中选择性地提取一部分属性,生成一个新的类型。这在某些情况下非常有用,比如当你只需要部分属性时,Pick 就可以派上用场了。例如,我们有一个 User 接口:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

如果我们只需要 idname 这两个属性,就可以使用 Pick

type PickUser = Pick<User, 'id' | 'name'>;

这样,PickUser 类型就只包含 idname 这两个属性。

使用场景

1. 提取部分用户信息

在处理用户信息时,可能只需要部分字段,而不是整个用户对象。Pick<T, K> 可以帮助你提取这些部分字段。

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

function getUserInfo(user: User): Pick<User, 'id' | 'name'> {
    return { id: user.id, name: user.name };
}

const user: User = { id: 1, name: "John", email: "john@example.com", age: 30 };
const userInfo = getUserInfo(user);
console.log(userInfo); // { id: 1, name: "John" }

2. 提取部分表单数据

在处理表单数据时,可能只需要部分字段,而不是整个表单数据。Pick<T, K> 可以帮助你提取这些部分字段。

type FormData = {
    username: string;
    password: string;
    confirmPassword: string;
};

function getFormUsername(data: FormData): Pick<FormData, 'username'> {
    return { username: data.username };
}

const formData: FormData = { username: "john_doe", password: "123456", confirmPassword: "123456" };
const formUsername = getFormUsername(formData);
console.log(formUsername); // { username: "john_doe" }

Pick 的实现原理

Pick 的实现原理是通过指定一个联合类型中的键,从原类型中提取这些键对应的属性。它的定义如下:

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

这里的关键是 [P in K],它表示遍历联合类型 K 中的每个键,并从类型 T 中提取这些键对应的属性。

八、Omit:排除特定属性

Omit 的作用是从一个类型中排除特定的属性,生成一个新的类型。这在某些情况下非常有用,比如当你需要排除某些不需要的属性时,Omit 就可以派上用场了。例如,我们有一个 User 接口:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

如果我们需要一个不包含 emailageUser 类型,就可以使用 Omit

type OmitUser = Omit<User, 'email' | 'age'>;

这样,OmitUser 类型就只包含 idname 这两个属性。

使用场景

1. 排除敏感信息

在处理用户信息时,可能需要排除某些敏感信息,如 emailageOmit<T, K> 可以帮助你排除这些敏感信息。

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

function getUserPublicInfo(user: User): Omit<User, 'email' | 'age'> {
    return { id: user.id, name: user.name };
}

const user: User = { id: 1, name: "John", email: "john@example.com", age: 30 };
const userPublicInfo = getUserPublicInfo(user);
console.log(userPublicInfo); // { id: 1, name: "John" }

2. 排除不必要的表单字段

在处理表单数据时,可能需要排除某些不必要的字段,如 confirmPasswordOmit<T, K> 可以帮助你排除这些字段。

type FormData = {
    username: string;
    password: string;
    confirmPassword: string;
};

function getFormUsernamePassword(data: FormData): Omit<FormData, 'confirmPassword'> {
    return { username: data.username, password: data.password };
}

const formData: FormData = { username: "john_doe", password: "123456", confirmPassword: "123456" };
const formUsernamePassword = getFormUsernamePassword(formData);
console.log(formUsernamePassword); // { username: "john_doe", password: "123456" }

Omit 的实现原理

Omit 的实现原理是通过排除指定的键,从原类型中生成一个新的类型。它的定义如下:

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

这里的关键是 Exclude<keyof T, K>,它表示从 keyof T 中排除 K 中的键,然后通过 Pick 提取剩余的键对应的属性。

九、NonNullable:排除 null 和 undefined

NonNullable 的作用是从一个类型中排除 nullundefined,生成一个新的类型。这在某些情况下非常有用,比如当你需要确保某个值不是 nullundefined 时,NonNullable 就可以派上用场了。例如,我们有一个联合类型 Value

type Value = string | null | undefined;

如果我们需要一个不包含 nullundefinedValue 类型,就可以使用 NonNullable

type NonNullableValue = NonNullable<Value>;

这样,NonNullableValue 类型就只包含 string

使用场景

1. 确保函数参数不为 null 或 undefined

在定义函数时,可能需要确保某些参数不是 nullundefinedNonNullable<T> 可以帮助你实现这一点。

type Value = string | null | undefined;

function processValue(value: NonNullable<Value>) {
    console.log(value.toUpperCase());
}

processValue("hello"); // "HELLO"
// processValue(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string'.
// processValue(undefined); // Error: Argument of type 'undefined' is not assignable to parameter of type 'string'.

2. 确保数组元素不为 null 或 undefined

在处理数组时,可能需要确保数组中的某些元素不是 nullundefinedNonNullable<T> 可以帮助你实现这一点。

type Value = string | null | undefined;

function filterNonNullValues(values: Value[]): NonNullable<Value>[] {
    return values.filter(value => value !== null && value !== undefined) as NonNullable<Value>[];
}

const values: Value[] = ["hello", null, "world", undefined];
const nonNullValues

NonNullable 的实现原理

NonNullable 的实现原理是通过 Exclude 来排除 nullundefined。它的定义如下:

type NonNullable<T> = Exclude<T, null | undefined>;

这里的关键是 Exclude<T, null | undefined>,它表示从类型 T 中排除 nullundefined。这种方式使得 NonNullable 能够从联合类型中移除 nullundefined,生成一个新的类型。

十、ReturnType:获取函数返回值类型

ReturnType 的作用是从一个函数类型中提取其返回值类型。这在某些情况下非常有用,比如当你需要对函数的返回值进行进一步处理或类型检查时,ReturnType 就可以派上用场了。例如,我们有一个函数 getUser

function getUser(): { id: number; name: string } {
    return { id: 1, name: "John" };
}

如果我们需要获取 getUser 函数的返回值类型,就可以使用 ReturnType

type UserType = ReturnType<typeof getUser>;

这样,UserType 类型就是 { id: number; name: string }

使用场景

1. 对函数返回值进行进一步处理

在处理函数返回值时,可能需要对返回值进行进一步处理。ReturnType<T> 可以帮助你获取函数的返回值类型,从而进行更精确的类型检查。

function getUser(): { id: number; name: string } {
    return { id: 1, name: "John" };
}

function processUser(user: ReturnType<typeof getUser>) {
    console.log(user.id, user.name);
}

const user = getUser();
processUser(user); // 1 "John"

2. 确保函数返回值符合预期

在定义函数时,可能需要确保函数的返回值符合特定的类型。ReturnType<T> 可以帮助你进行类型检查。

function getNumber(): number {
    return 42;
}

function getString(): string {
    return "hello";
}

function processValue<T>(value: ReturnType<T>) {
    console.log(value);
}

processValue(getNumber); // 42
processValue(getString); // "hello"

3. 对异步函数返回值进行处理

在处理异步函数返回值时,可能需要对返回值进行进一步处理。ReturnType<T> 可以帮助你获取异步函数的返回值类型,从而进行更精确的类型检查。

async function fetchData(): Promise<{ data: string }> {
    return { data: "fetched data" };
}

async function processData(): Promise<string> {
    const result = await fetchData();
    return result.data;
}

function handleResult(result: ReturnType<typeof processData>) {
    console.log(result);
}

processData().then(handleResult); // "fetched data"

ReturnType 的实现原理

ReturnType 的实现原理是通过条件类型来提取函数的返回值类型。它的定义如下:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

这里的关键是 infer R,它表示从函数类型 T 中推断返回值类型 R。这种方式使得 ReturnType 能够从函数类型中提取返回值类型。

十一、Parameters:获取函数参数类型

Parameters 的作用是从一个函数类型中提取其参数类型。这在某些情况下非常有用,比如当你需要对函数的参数进行进一步处理或类型检查时,Parameters 就可以派上用场了。例如,我们有一个函数 add

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

如果我们需要获取 add 函数的参数类型,就可以使用 Parameters

type AddParameters = Parameters<typeof add>;

这样,AddParameters 类型就是 [number, number]

使用场景

1. 对函数参数进行进一步处理

在处理函数参数时,可能需要对参数进行进一步处理。Parameters<T> 可以帮助你获取函数的参数类型,从而进行更精确的类型检查。

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

function processAddParameters(params: Parameters<typeof add>) {
    console.log(params[0], params[1]);
}

const params = [1, 2];
processAddParameters(params); // 1 2

2. 确保函数参数符合预期

在定义函数时,可能需要确保函数的参数符合特定的类型。Parameters<T> 可以帮助你进行类型检查。

function multiply(a: number, b: number): number {
    return a * b;
}

function processMultiplyParameters(params: Parameters<typeof multiply>) {
    console.log(params[0] * params[1]);
}

const params = [3, 4];
processMultiplyParameters(params); // 12

Parameters 的实现原理

Parameters 的实现原理是通过条件类型来提取函数的参数类型。它的定义如下:

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

这里的关键是 infer P,它表示从函数类型 T 中推断参数类型 P。这种方式使得 Parameters 能够从函数类型中提取参数类型。


十二、InstanceType:获取构造函数返回的实例类型

InstanceType 的作用是从一个构造函数类型中提取其返回的实例类型。这在某些情况下非常有用,比如当你需要对构造函数的实例进行进一步处理或类型检查时,InstanceType 就可以派上用场了。例如,我们有一个构造函数 User

class User {
    constructor(public id: number, public name: string) {}
}
type UserInstance = InstanceType<typeof User>;

这样,UserInstance 类型就是 User 的实例类型。

使用场景

1. 对构造函数实例进行进一步处理

在处理构造函数实例时,可能需要对实例进行进一步处理。InstanceType<T> 可以帮助你获取构造函数的实例类型,从而进行更精确的类型检查。

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

function processUserInstance(instance: InstanceType<typeof User>) {
    console.log(instance.id, instance.name);
}

const user = new User(1, "John");
processUserInstance(user); // 1 "John"

2. 确保构造函数实例符合预期

在定义构造函数时,可能需要确保构造函数的实例符合特定的类型。InstanceType<T> 可以帮助你进行类型检查。

class Product {
    constructor(public id: number, public name: string, public price: number) {}
}

function processProductInstance(instance: InstanceType<typeof Product>) {
    console.log(instance.id, instance.name, instance.price);
}

const product = new Product(1, "Laptop", 1200);
processProductInstance(product); // 1 "Laptop" 1200

InstanceType 的实现原理

InstanceType 的实现原理是通过条件类型来提取构造函数的实例类型。它的定义如下:

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

这里的关键是 infer R,它表示从构造函数类型 T 中推断实例类型 R。这种方式使得 InstanceType 能够从构造函数类型中提取实例类型。