好的,这有点难,我会解释一下。 TL;DR。
typescript 中的参数名称仅在传播原始参数时保留,例如:
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 的情况,在扩展中使用原始参数名称(但是,名称没有语义意义,并且否则无法观察到)。
但是,这可以使用参数元组上的映射类型来完成,因为映射元组保留参数名称,并且考虑到“最后一个”项目问题的一些后处理。
- 为了“改变”参数,我们可以使用映射类型。映射类型适用于对象以外的元组,而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]
}
- 这里的问题是我们不知道最后一个索引,因为您正在为函数提供动态数量的参数。要计算最后一个,我们可以使用一个欺骗 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",我们的最后一项。请注意,我们在这里使用类型推断和函数 rest 推断。
- 我们只是把所有东西放在一起,添加一个类型来传递一个函数 (
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
这很有趣!希望这能回答你的问题,但是如果你真的在你的应用程序中需要这个,我希望你知道你在做什么,因为这个要求可能可以用更简单的方式来解决。