Заменить тип последнего параметра в машинописи - PullRequest
1 голос
/ 25 апреля 2020

Я хочу заменить тип последнего параметра в функции, сохранив имена всех параметров функции. В моем случае последний аргумент также необязателен.

Например:

type OrigArg = { arg: number };
type ReplacedArg = { arg: string };

type OrigFunc = (a: number, b: string, c?: OrigArg) => string
type ReplaceLast<TFunc, TReplace> = // type I'm looking for

type ReplacedFunc = ReplaceLast<OrigFunc, ReplacedArg> 
// type ReplacedFunc = (a: number, b: string, c?: ReplacedArg) => string

Еще одно усложнение заключается в том, что число и тип предыдущих аргументов являются переменными, я просто знаю, что последний аргумент будет определенного типа, и я хочу заменить его на пользовательский.

1 Ответ

1 голос
/ 25 апреля 2020

Хорошо, это немного сложно, и я собираюсь объяснить это. Go вниз для TL; DR .

Имена параметров в машинописи сохраняются только при расширении исходных параметров, например:

type OrigParams = Parameters<OrigFunc>;
type AnotherFunc = (...args: OrigParams) => any;

Это связано с природой TS где кортеж Parameters действительно особенный, в котором имена хранятся как внутренние, недоступные, метаданные, которые уничтожаются при смене кортежа или просто при создании другого. то есть:

type F = (a: any, b: any) => void;
type P1 = Parameters<F>;
type P2 = [any, any];

здесь P1 и P2 - это разные типы, даже если они структурно одинаковы.

Цитирование TS:

Обратите внимание, что когда тип кортежа выводится из последовательности параметров, а затем разворачивается в список параметров, как в случае с U, в расширении используются исходные имена параметров (однако имена не имеют значения semanti c и не в противном случае это можно наблюдать).

Однако это можно сделать с помощью сопоставленных типов в кортеже параметров, поскольку при сопоставлении кортежа сохраняется имен параметров и немного заданной последующей обработки проблема «последнего» элемента.

  1. Чтобы «изменить» параметр, мы можем использовать Mapped Type. Сопоставленные типы применимы к кортежам, отличным от объектов, и Parameters - это кортеж, поэтому мы придумали:
type ReplaceLastParam<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: // We should put the replace code here
}
Проблема в том, что мы не должны изменять все параметры, но только последний . K - это string при отображении типов, т.е. keyof TParams равно "0" | "1" | "2" в случае кортежа с тремя элементами. Таким образом, в случае, если у нас есть только три параметра, мы можем легко написать:
type ReplaceParam2<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: K extends "2" ? TReplace : TParams[K] 
}
Проблема в том, что мы не знаем последний индекс, так как вы предоставляете функции с динамическим c числом аргументов. Чтобы вычислить последний, мы можем использовать помощник, который обманывает TS:
type LastIndex<T extends readonly any[]> =
    ((...t: T) => void) extends ((x: any, ...r: infer R) => void) ?  Exclude<keyof T, keyof R> : never;

Этот тип вычисляет ключи кортежа R, который на один элемент меньше кортежа T и различает их с Exclude: так что Exclude<"0" | 1" | "2", "0" | "1"> - это точно "2", наш последний элемент. Обратите внимание, что мы используем здесь вывод типа и вывод отдыха функции.

Мы просто соединили все вместе, добавив тип для передачи функции (ReplaceLast) для переноса параметров, изменяющих тип, который мы только что сделали:

TL; DR :

// Index helper
type LastIndex<T extends readonly any[]> =
    ((...t: T) => void) extends ((x: any, ...r: infer R) => void) ?  Exclude<keyof T, keyof R> : never;

// Replace last parameter with mapped type
type ReplaceLastParam<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: K extends LastIndex<TParams> ? TReplace : TParams[K] 
}

// Replace function
type ReplaceLast<F, TReplace> = F extends (...args: infer T) => infer R
    ? (...args: ReplaceLastParam<T, TReplace>) => R
    : never;

Playground Link

Это было весело! Надеюсь, что это ответ на ваш вопрос, однако, если вам действительно нужно это в вашем приложении, я надеюсь, что вы знаете, что делаете, поскольку требование может быть решено более простым способом.

...