Предположим, у меня есть тип функции, например.
type somefntype = (a: number, b: string, c: boolean) => void
Мне нужен тип последнего параметра типа этой функции:
type lastparamtype = LastParameter<somefntype> // lastparamtype == boolean
Как определить LastParameter
?
Для функции с 3 аргументами решение будет
type somefntype = (a: number, b: string, ddd: boolean) => void
type LastParameter<T> = T extends (a: infer A1, b: infer A2, c: infer A3) => any ? A3 : never;
type lastArg = LastParameter<somefntype>; // === boolean
Имя функции не имеет значения.
Для функции с произвольным количеством аргументов задача немного усложняется. Мы можем использовать ...rest
, но это будет массив.
type LastParameter<T> = T extends (...rest: infer A3) => any ?
A3 : never;
type lastArg = LastParameter<somefntype>; // lastArg === [number, string, boolean]
Получить последний элемент результирующего кортежа немного сложно. Одно возможное решение было вдохновлено этим статья.
type Prev<T extends number> = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62][T];
type GetLength<original extends any[]> = original extends { length: infer L } ? L : never;
type GetLast<original extends any[]> = original[Prev<GetLength<original>>]
type somefntype = (a: number, b: string, ddd: boolean) => void
type LastParameter1<T> = T extends (...rest: infer A3) => any ?
A3 extends any[] ? GetLast<A3> : never
: never;
type lastArg = LastParameter<somefntype>; // boolean for 3 arg function
Второе решение имеет ограничение на количество аргументов функции.
Обновление: TypeScript 4.0 представил вариативные типы кортежей, что означает, что Last
теперь можно реализовать просто как
type Last<T extends any[]> = T extends [...infer I, infer L] ? L : never;
А в остальном работает как обычно:
type LastParameter<F extends (...args: any)=>any> = Last<Parameters<F>>;
type somefntype = (a: number, b: string, c: boolean) => void
type lastparamtype = LastParameter<somefntype> // boolean
Ссылка на код для игровой площадки
TypeScript 3.0 представил типы кортежей в остальных и расширенных выражениях, специально для обеспечения программного преобразования между типами списков параметров функций и кортежи. Существует псевдоним типа Parameters<F>
определено в стандартной библиотеке, который возвращает список параметров функции в виде кортежа.
Это раздражает, но возможно получить последний элемент типа кортежа с фиксированной, но произвольной длиной:
// Tail<T> returns a tuple with the first element removed
// so Tail<[1, 2, 3]> is [2, 3]
// (works by using rest tuples)
type Tail<T extends any[]> =
((...t: T)=>void) extends ((h: any, ...r: infer R)=>void) ? R : never;
// Last<T> returns the last element of the tuple
// (works by finding the one property key in T which is not in Tail<T>)
type Last<T extends any[]> = T[Exclude<keyof T, keyof Tail<T>>];
Объединив их, вы получите
type LastParameter<F extends (...args: any)=>any> = Last<Parameters<F>>;
И мы можем проверить это:
type somefntype = (a: number, b: string, c: boolean) => void
type lastparamtype = LastParameter<somefntype> // boolean
Выглядит хорошо для меня. Ссылка на код
Вот еще одно решение, которое представляет собой небольшую модификацию ответа jcalz, чтобы сделать его более общим, используя некоторые рекурсивные условные типы:
type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
type Tail<T extends any[]> =
((...t: T) => any) extends ((_: any, ...tail: infer U) => any)
? U
: [];
type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
type Last<T extends any[]> = {
0: Last<Tail<T>>
1: Head<T>
}[
HasTail<T> extends true ? 0 : 1
];
type LastType = Last<Parameters<somefntype>>; // boolean
Было бы интересно подумать о замене any
на unknown
.
Я думаю, что было бы лучше использовать unknown
вместо any
: type Head<T extends unknown[]> = T extends [unknown, ...unknown[]] ? T[0] : never
Начиная с TypeScript 4.0 с поддержкой вариативного кортежа также можно использовать следующее, чтобы получить последний тип в типе кортежа:
type LastType<T extends [unknown, ...Array<unknown>]> = T extends [...infer A, infer L] ? L : never;
К сожалению, рекурсивные условные типы — это не поддерживается. Официальное слово "не делай этого" и "мы не готовы поддерживать такого рода вещи". На данный момент я бы оставил такой код в песочнице для развлечения, но не позволял ему приближаться к рабочей/общей среде.