【问题标题】:Replace the type of the last parameter in a function typescript替换函数打字稿中最后一个参数的类型
【发布时间】:2020-04-25 10:03:11
【问题描述】:

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

例如:

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

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

【问题讨论】:

    标签: typescript parameters tuples


    【解决方案1】:

    好的,这有点难,我会解释一下。 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];
    

    这里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&lt;"0" | 1" | "2", "0" | "1"&gt; 正好是"2",我们的最后一项。请注意,我们在这里使用类型推断和函数 rest 推断。

    1. 我们只是把所有东西放在一起,添加一个类型来传递一个函数 (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

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

    【讨论】:

    • 太棒了,你不知道我在试图得到这个的过程中遇到的困难——其中大部分最终都将函数 args 名称归零。对于上下文,我的具体用例是编写流行库的扩展。这些函数中的每一个:docs.ethers.io/ethers.js/html/… 都有一个覆盖属性,因为它是最后一个参数 docs.ethers.io/ethers.js/html/api-contract.html#overrides,我可以用一些自定义的覆盖来替换这些覆盖。
    • 啊,听起来很合理!是的,TS中的这种东西真的是一个难题
    • 不错!没有意识到keyof在映射元组/数组时会产生索引
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-28
    • 2019-03-20
    • 2022-01-12
    • 1970-01-01
    • 2019-07-05
    • 2022-11-11
    相关资源
    最近更新 更多