Хорошо, это немного сложно, и я собираюсь объяснить это. 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 и не в противном случае это можно наблюдать).
Однако это можно сделать с помощью сопоставленных типов в кортеже параметров, поскольку при сопоставлении кортежа сохраняется имен параметров и немного заданной последующей обработки проблема «последнего» элемента.
- Чтобы «изменить» параметр, мы можем использовать 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
Это было весело! Надеюсь, что это ответ на ваш вопрос, однако, если вам действительно нужно это в вашем приложении, я надеюсь, что вы знаете, что делаете, поскольку требование может быть решено более простым способом.