替换函数打字稿中最后一个参数的类型

时间:2020-04-25 10:03:11

标签: typescript parameters tuples

我想在保留所有函数参数名称的同时替换函数中最后一个参数的类型。就我而言,最后一个参数也是可选的。

例如:

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 个答案:

答案 0 :(得分:3)

好的,这有点困难,我将对其进行解释。进入 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];

此处P1P2是不同的类型,即使它们在结构上相同。

引用TS:

请注意,当从参数序列推断出元组类型并随后将其扩展到参数列表时(如U的情况),原始参数名称将在扩展中使用(但是,名称没有语义含义,否则无法观察到。)

但是,这可以通过使用参数元组上的映射类型来完成,因为映射元组保留参数名,并在出现“最后一个”项目问题时进行一些后期处理。

  1. 为了“更改”参数,我们可以使用映射类型。映射类型适用于对象以外的元组,而Parameters是元组,因此我们想到了:
type ReplaceLastParam<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: // We should put the replace code here
}
  1. 问题在于,我们不应更改所有参数,而只能更改最后一个。在映射类型时,Kstring,即在具有三个元素的元组的情况下,keyof TParams"0" | "1" | "2"。因此,在只有三个参数的情况下,我们可以轻松编写:
type ReplaceParam2<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: K extends "2" ? TReplace : TParams[K] 
}
  1. 这里的问题是我们不知道最后一个索引,因为您要为函数提供动态数量的参数。要计算最后一个,我们可以使用欺骗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">就是{{1 }},我们的最后一项。请注意,我们在这里使用类型推断,而函数将进行其他推断。

  1. 我们将所有内容放在一起,添加一个类型以传递一个函数("2"),以包装刚刚更改的参数更改类型:

TL; DR

ReplaceLast

Playground Link

这很有趣!希望这能回答您的问题,但是,如果您确实需要在应用程序中使用它,那么我希望您知道自己在做什么,因为可以用更简单的方式解决该要求。