Typescript中的类型展开运算符

10

我试图定义一个类型,它将函数类型作为泛型参数,返回一个函数类型,该函数类型与输入的函数类型相同,只是在末尾多了一个参数:

  type AugmentParam<F extends (...args: any[]) => any, ExtraParam> = F extends (
    ...args: infer Args
  ) => infer R
    ? (
        ...args: [
          ...Args,
          ExtraParam
        ]
      ) => R
    : never

一个使用示例:

type F = (x: number) => boolean
type F2 = AugmentParam<F, string> // (x: number, arg2: string) => boolean

...Args 似乎不起作用,但是如果我将其更改为这样,它就可以工作:

  type AugmentParam<F extends (...args: any[]) => any, ExtraParam> = F extends (
    ...args: infer Args
  ) => infer R
    ? (
        ...args: [
          Args[0],
          Args[1] /* Spread doesn't work here, so it doesn't work for arbitrary number of arguments :( */,
          ExtraParam
        ]
      ) => R
    : never

但它只适用于特定数量的参数,我需要为每个n元函数定义一个这样的类型。

1个回答

8

TypeScript可以很容易地将一个类型添加到元组类型的开头,称为 Cons<H,T>,如下所示:

type Cons<H, T extends readonly any[]> =
    ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never

type ConsTest = Cons<1, [2, 3, 4]>;
// type ConsTest = [1, 2, 3, 4]

您可以使用这个定义,结合条件映射的元组类型,来生成一个Push<T, V>,以在元组末尾追加一个类型:
type Push<T extends readonly any[], V> = Cons<any, T> extends infer A ?
    { [K in keyof A]: K extends keyof T ? T[K] : V } : never

type PushTest = Push<[1, 2, 3], 4>;
// type PushTest = [1, 2, 3, 4]

但是这个Push方法的定义过于脆弱。如果T元组中有可选元素,或者它来自于函数的参数列表,您会注意到编译器将可选标记和参数名向右移动了一个元素:

type Hmm = (...args: Push<Parameters<(optStrParam?: string) => void>, number>) => void;
// type Hmm = (h: string | undefined, optStrParam?: number) => void

参数名称并不是类型的一部分,所以虽然这很烦人,但并不影响实际类型。在可选参数之后添加参数会有点奇怪,因此我不确定正确的行为是什么。不确定这些问题是否会让你无法接受,但请注意。

无论如何,您的AugmentParam应该如下:

type AugmentParam<F extends (...args: any[]) => any, ExtraParam> =
    (...args: Extract<Push<Parameters<F>, ExtraParam>, readonly any[]>)
        => ReturnType<F>

并且它也可以正常工作(前提是需要注意之前提到的警告):

type F = (x: number) => boolean

type F2 = AugmentParam<F, string>
// type F2 = (h: number, x: string) => boolean

type F3 = AugmentParam<F2, boolean>
// type F3 = (h: number, h: string, x: boolean) => boolean

好的,希望有所帮助。祝您好运!

代码链接


哇!非常感谢你! - Alireza Mirian
似乎“shift”问题对我的情况很重要。 我试图将其与此类型一起使用: (text: string, html: string|undefined, editorState: EditorState) => DraftHandleValue 我定义了一个类似于这样的类型:AugmentParam<EditorProps['handleKeyCommand'], PluginFunctions>,但它似乎没有按预期工作。 - Alireza Mirian
抱歉,这对我来说不足以提供帮助。您能否将您的问题表示为一个 [mcve]? - jcalz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接