Exclude, Extract, NonNullable, ReturnType, InstanceType, Omit, Awaited
Чтобы сэкономить время разработчиков, в систему типов TypeScript были включены несколько часто требующихся условных типов, каждый из которых будут подробно рассмотрен в этой главе.
Exclude<T, U> (исключает из T признаки присущие U)
В результате разрешения условный тип Exclude<T, U>
будет представлять разницу типа T
относительно типа U
. Параметры типа T
и U
могут быть представлены как единичным типом, так и множеством union
.
// @filename: lib.d.ts type Exclude<T, U> = T extends U ? never : T;
Простыми словами из типа T
будут исключены признаки (ключи) присущие также и типу U
.
let v0: Exclude<number|string, number|boolean>; // let v0: string let v1: Exclude<number|string, boolean|object>; // let v1: string|number let v2: Exclude<"a" | "b", "a" | "c">; // let v2: "b"
В случае, если оба аргумента типа принадлежат к одному и тому же типу данных, Exclude<T, U>
будет представлен типом never
.
let v4: Exclude<number|string, number|string>; // let v4: never
Его реальную пользу лучше всего продемонстрировать на реализации функции, которая на входе получает два разных объекта, а на выходе возвращает новый объект, состоящий из членов присутствующих в первом объекте, но отсутствующих во втором. Аналог функции difference
из широко известной библиотеки lodash.
declare function difference<T, U>(a: T, b: U): Pick<T, Exclude<keyof T, keyof U>> interface IA { a: number; b: string; } interface IB { a: number; c: boolean; } let a: IA = { a: 5, b: '' }; let b: IB = { a: 10, c: true }; interface IDifference { b: string; } let v0: IDifference = difference(a, b); // Ok let v1: IA = difference(a, b); // Error -> Property 'a' is missing in type 'Pick<IA, "b">' but required in type 'IA'. let v2: IB = difference(a, b); // Error -> Type 'Pick ' is missing the following properties from type 'IB': a, c
Extract<T, U> (общие для двух типов признаки)
В результате разрешения условный тип Extract<T, U>
будет представлять пересечение типа T
относительно типа U
. Оба параметра типа могут быть представлены как обычным типом, так union
.
// @filename: lib.d.ts type Extract<T, U> = T extends U ? T : never;
Простыми словами, после разрешения Extract<T, U>
будет принадлежать к типу определяемого признаками (ключами) присущих обоим типам. То есть, тип Extract<T, U>
является противоположностью типа Exclude<T, U>
.
let v0 :Extract<number|string, number|string>; // let v0: string | number let v1 :Extract<number|string, number|boolean>; // let v1: number let v2 :Extract<"a" | "b", "a" | "c">; // let v2: "a"
В случае, когда общие признаки отсутствуют, тип Extract<T, U>
будет представлять тип never
.
let v3 :Extract<number|string, boolean|object>; // let v3: never
Условный тип Extract<T, U>
стоит рассмотреть на примере реализации функции принимающей два объекта и возвращающей новый объект, состоящий из членов первого объекта, которые также присутствуют и во втором объекте.
declare function intersection<T, U>(a: T, b: U): Pick<T, Extract<keyof T, keyof U>> interface IA { a: number; b: string; } interface IB { a: number; c: boolean; } let a: IA = { a: 5, b: '' }; let b: IB = { a: 10, c: true }; interface IIntersection { a: number; } let v0: IIntersection = intersection(a, b); // Ok let v1: IA = intersection(a, b); // Error -> Property 'b' is missing in type 'Pick<IA, "a">' but required in type 'IA'. let v2: IB = intersection(a, b); // Error -> Property 'c' is missing in type 'Pick<IA, "a">' but required in type 'IB'.
NonNullable<T> (удаляет типы null и undefined)
Условный тип NonNullable<T>
служит для исключения из типа признаков типов null
и undefined
. Единственный параметр типа может принадлежать как к обычному типу, так и множеству определяемого тип union
.
// @filename: lib.d.ts type NonNullable<T> = T & {};
Простыми словами, данный тип удаляет из аннотации типа такие типы, как null
и undefined
.
let v0: NonNullable<string | number | null>; // let v0: string | number let v1: NonNullable<string | undefined | null>; // let v1: string let v2: NonNullable<string | number | undefined | null>; // let v2: string | number
В случае, когда тип, выступающий в роли единственного аргумента типа, принадлежит только к типам null
и\или undefined
, NonNullable<T>
представляет тип never
.
let v3: NonNullable<undefined | null>; // let v3: never
ReturnType<T> (получить тип значения возвращаемого функцией)
Условный тип ReturnType<T>
служит для установления возвращаемого из функции типа. В качестве параметра типа должен обязательно выступать функциональный тип.
// @filename: lib.d.ts type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
На практике очень часто требуется получить тип к которому принадлежит значение возвращаемое из функции. Единственное на, что стоит обратить внимание, что в случаях, когда тип возвращаемого из функции значения является параметром типа, у которого отсутствуют хоть какие-то признаки, то тип ReturnType<T>
будет представлен пустым объектным типом {}
.
let v0: ReturnType<() => void>; // let v0: void let v1: ReturnType<() => number | string>; // let v1: string|number let v2: ReturnType<<T>() => T>; // let v2: {} let v3: ReturnType<<T extends U, U extends string[]>() => T>; // let v3: string[] let v4: ReturnType<any>; // let v4: any let v5: ReturnType<never>; // let v5: never let v6: ReturnType<Function>; // Error let v7: ReturnType<number>; // Error
InstanceType<T> (получить через тип класса тип его экземпляра)
Условный тип InstanceType<T>
предназначен для получения типа экземпляра на основе типа представляющего класс. Параметр типа T
должен обязательно принадлежать к типу класса.
// @filename: lib.d.ts type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
В большинстве случаев идентификатор класса задействован в приложении в качестве типа его экземпляра.
class Animal { move(): void {} } /** * Тип Animal представляет объект класса, *, то есть его экземпляр полученный при * помощи оператора new. */ function f(animal: Animal){ type Param = typeof Animal; // здесь Param представляет экземпляр типа Animal }
Но сложные приложения часто требуют динамического создания своих компонентов. В таких случаях фабричные функции работают не с экземплярами классов, а непосредственно с самими классами.
Стоит напомнить, что в JavaScript классы, это всего-лишь синтаксический сахар над старой, доброй функцией конструктором. И как известно объект функции конструктора представляет объект класса содержащего ссылку на прототип, который и представляет экземпляр. Другими словами, в TypeScript идентификатор класса указанный в аннотации типа, представляет описание прототипа. Чтобы получить тип самого класса, необходимо выполнить над идентификатором класса запрос типа.
class Animal { move(): void {} } type Instance = Animal; type Class = typeof Animal; type MoveFromInstance = Instance["move"]; // Ok ->() => void type MoveFromClass = Class["move"]; // Error -> Property 'move' does not exist on type 'typeof Animal'.
Таким образом, грамотно вычислить тип экземпляра в фабричной функции можно при помощи типа InstanceType<T>
.
class Animal { move(): void {} } function factory(Class: typeof Animal){ type Instance = InstanceType<Class>; let instance: Instance = new Class(); // Ok -> let instance: Animal }
Хотя можно прибегнуть и к менее декларативному способу к запросу типа свойства класса prototype
.
function factory(Class: typeof Animal){ type Instance = Class["prototype"]; let instance: Instance = new Class(); // Ok -> let instance: Animal }
И последнее о чем стоит упомянуть, что результат получение типа непосредственно через any
и never
будет представлен ими же. Остальные случаи приведут к возникновению ошибки.
class Animal {} let v0: InstanceType<any>; // let v0: any let v1: InstanceType<never>; // let v1: never let v2: InstanceType<number>; // Error
Parameters<T> (получить тип размеченного кортежа описывающий параметры функционального типа)
Расширенный тип Parameters<T>
предназначен для получения типов указанных в аннотации параметров функции. В качестве аргумента типа ожидается функциональный тип, на основе которого будет получен размеченный кортеж описывающий параметры этого функционального типа.
type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;
Parameters<T>
возвращает типы параметров в виде кортежа.
function f<T>(p0: T, p1: number, p2: string, p3?: boolean, p4: object = {}) { } /** * type FunctionParams = [p0: unknown, p1: number, p2: string, p3?: boolean, p4?: object] */ type FunctionParams = Parameters<typeof f>;
ConstructorParameters<T> (получить через тип класса размеченный кортеж описывающий параметры его конструктора)
Расширенный тип ConstructorParameters<T>
предназначен для получения типов указанных в аннотации параметров конструктора.
type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;
В качестве единственного параметра типа ConstructorParameters<T>
ожидает тип самого класса, на основе конструктора которого будет получен размеченный кортеж описывающий параметры этого конструктора.
class Class<T> { constructor(p0: T, p1: number, p2: string, p3?: boolean, p4: object = {}) {} } /** * type ClassParams = [p0: unknown, p1: number, p2: string, p3?: boolean, p4?: object] */ type ClassParams = ConstructorParameters<typeof Class>;
Omit<T, K> (исключить из T признаки ассоциированными с ключами перечисленных множеством K)
Расширенный тип Omit<T, K>
предназначен для определения нового типа путем исключения заданных признаков из существующего тип.
// lib.d.ts type Omit<T, K extends string | number | symbol> = { [P in Exclude<keyof T, K>]: T[P]; }
В качестве первого аргумента типа тип Omit<T, K>
ожидает тип данных, из которого будут исключены признаки, связанные с ключами, переданными в качестве второго аргумента типа.
Простыми словами, к помощи Omit<T, K>
следует прибегать в случаях необходимости определения типа, представляющего некоторую часть уже существующего типа.
type Person = { firstName: string; lastName: string; age: number; }; /** * Тип PersonName представляет только часть типа Person * * type PersonName = { * firstName: string; * lastName: string; * } */ type PersonName = Omit<Person, 'age'>; // исключение признаков связанных с полем age из типа Person
Awaited<T> (рекурсивное развертывания промисов)
Расширенный тип Awaited<T>
предназначен для рекурсивного развертывания промисов, что в повседневной работе с асинхронными операциями является незаменимым помощником.
// A = string type A = Awaited<Promise<string>>; // B = string type B = Awaited<Promise<Promise<string>>>; // C = string | number type C = Awaited<string | Promise<number>>;