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

yah*_*wai 4 parameters tuples typescript

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

例如:

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
Run Code Online (Sandbox Code Playgroud)

更复杂的是前面的参数的数量和类型是可变的,我只知道最后一个参数将是某种类型,我想用自定义的类型替换它。

leo*_*ory 8

好吧,这有点困难,我将解释一下。往下看TL;DR

仅在传播原始参数时才保留打字稿中的参数名称,例如:

type OrigParams = Parameters<OrigFunc>;
type AnotherFunc = (...args: OrigParams) => any;
Run Code Online (Sandbox Code Playgroud)

这是由于 TS 的本质,其中Parameters元组确实是一个特殊的元组,它将名称保留为内部的、不可访问的元数据,在更改元组或创建另一个元组时,这些元数据会被破坏。IE:

type F = (a: any, b: any) => void;
type P1 = Parameters<F>;
type P2 = [any, any];
Run Code Online (Sandbox Code Playgroud)

即使结构相同,这里P1和也是不同的类型。P2

引用 TS:

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

但是,这可以通过使用参数元组上的映射类型来完成,因为映射元组保留了参数名称,并且在考虑到“最后”项问题的情况下进行了一些后处​​理。

  1. 为了“更改”参数,我们可以使用映射类型。映射类型适用于除对象之外的元组,而参数是一个元组,因此我们提出:
type ReplaceLastParam<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: // We should put the replace code here
}
Run Code Online (Sandbox Code Playgroud)
  1. 问题是我们不应该更改所有参数,而只能更改最后一个参数K是一个stringwhile 映射类型,即keyof TParams具有"0" | "1" | "2"三个元素的元组的情况。因此,在只有三个参数的情况下,我们可以轻松编写:
type ReplaceParam2<TParams extends readonly any[], TReplace> = {
    [K in keyof TParams]: K extends "2" ? TReplace : TParams[K] 
}
Run Code Online (Sandbox Code Playgroud)
  1. 这里的问题是我们不知道最后一个索引,因为您提供的函数具有动态数量的参数。为了计算最后一个,我们可以使用一个欺骗 TS 的助手:
type LastIndex<T extends readonly any[]> =
    ((...t: T) => void) extends ((x: any, ...r: infer R) => void) ?  Exclude<keyof T, keyof R> : never;
Run Code Online (Sandbox Code Playgroud)

这种类型计算一个元组的键R,该元组的元素比元组少一个T,并用 来区分它们Exclude:所以Exclude<"0" | 1" | "2", "0" | "1">正是"2"我们的最后一项。请注意,我们在这里使用类型推断和函数剩余推断。

  1. 我们只是将所有内容放在一起,添加一个类型来传递函数 ( ReplaceLast) 来包装我们刚刚创建的参数更改类型:

长话短说

// 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;
Run Code Online (Sandbox Code Playgroud)

游乐场链接

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