Typescript: получить тип последнего параметра типа функции

Предположим, у меня есть тип функции, например.

type somefntype = (a: number, b: string, c: boolean) => void

Мне нужен тип последнего параметра типа этой функции:

type lastparamtype = LastParameter<somefntype> // lastparamtype == boolean

Как определить LastParameter?

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
12
0
2 384
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Для функции с 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.

К сожалению, рекурсивные условные типы — это не поддерживается. Официальное слово "не делай этого" и "мы не готовы поддерживать такого рода вещи". На данный момент я бы оставил такой код в песочнице для развлечения, но не позволял ему приближаться к рабочей/общей среде.

jcalz 30.05.2019 14:59

Я думаю, что было бы лучше использовать unknown вместо any: type Head<T extends unknown[]> = T extends [unknown, ...unknown[]] ? T[0] : never

macabeus 06.03.2020 19:39

Начиная с TypeScript 4.0 с поддержкой вариативного кортежа также можно использовать следующее, чтобы получить последний тип в типе кортежа:

type LastType<T extends [unknown, ...Array<unknown>]> = T extends [...infer A, infer L] ? L : never;

Другие вопросы по теме