带有有序数组/元组的 Typescript 泛型

Typescript Generics with ordered array / tuples

我有一个函数,它有两个参数:

  1. 对象键列表
  2. 一个只有一个参数的函数:对象键的值

没关系,如果内部函数以数组形式获取值:

myFunction<T>(params: (keyof T)[], innerFn: (T[keyof T][]) => any)

或作为对象:

myFunction<T>(params: (keyof T)[], innerFn: ({[key in keyof T]: T[key]}) => any)

只要打字正常;-)

这两个函数签名当然没有按预期工作。

以下是我想如何使用它们的一些示例:

interface MyInterface {
    foo: string;
    bar: number;
    baz: boolean[];
}

myFunction<MyInterface>(
    ['foo'],
    ([foo]) => null    // foo should be typed as 'string'
);

myFunction<MyInterface>(
    ['abc'],    // should throw error, because there's no abc key in MyInterface
    ([abc]) => null
);

myFunction<MyInterface>(
    ['bar', 'baz'],
    ([bar, baz]) => null  // bar should be typed as 'number', baz as 'boolean[]'
);

函数签名应该:

  1. 根据提供的MyInterface限制params数组
  2. 根据 params 数组正确输入 innerFn

这可以使用 TypeScript 吗?

如果yes:如何实现?

你可以做一些你想做的事情,但有一些注意事项:

你必须将你的函数分解成几个:

泛型类型参数 T 需要由调用者显式指定,但函数参数也需要是泛型的(例如 A extends keyof T)。您不能使用带有 <T, A, B> 等参数的函数,其中 T 需要手动指定,但 AB 需要从参数中推断出来。您基本上得到 all or nothing from type parameter inference. One way to work around this is to use a curried function 其中 myFunction<T>() returns 另一个函数 <A, B> 作为类型参数。也就是这样改:

// F<> is just a placeholder
declare myFunction<T, A, B>(args: [A, B], funcs: F<T,A,B>): void;
// specify everything, annoying
myFunction<MyInterface, 'foo', 'bar'>(['foo', 'bar'], ([foo, bar])=>null);
// specify nothing, doesn't work
myFunction(['foo', 'bar'], ([foo, bar])=>null); // error

myFunction<T>(): <A, B>(args: [A, B], funcs: F<T,A,B>) => void
myFunction<MyInterface>()(['foo','bar'], ([foo, bar])=>null); // works

您可以看到它如何允许您在 myFunction<MyInterface>() 中指定 MyInterface 并允许编译器在后续调用中推断 AB

tuples很难通用:

TypeScript 缺少 variadic kinds, so it's not really possible to give a single signature to many functions which act on "tuples of any length". You end up needing to work around this by providing overloaded 作用于您关心的特定长度的元组的函数签名。 T 键元组到 T 相应值元组的“简单”映射,您希望将其表示为

declare myFunction<T,K extends keyof T>(
  keys: [...K], funcs: ([...T[K]]) => void): void;

最终扩展到

// one-tuple
declare myFunction<T, A extends keyof T>(
  keys: [A], funcs: ([T[A]) => void): void;
// two-tuple
declare myFunction<T, A extends keyof T, B extends keyof T>(
  keys: [A,B], funcs: ([T[A],T[B]) => void): void;
// three-tuple
declare myFunction<T, A extends keyof T, B extends keyof T, C extends keyof T>(
  keys: [A,B,C], funcs: ([T[A],T[B],T[C]) => void): void;
// et cetera

只要你能站得住。

放在一起:

那么,让我们像这样咖喱和重载:

declare function myFunction<T>(): {
  <A extends keyof T, B extends keyof T, C extends keyof T, D extends keyof T>(
    a: [A, B, C, D], f: (a: [T[A], T[B], T[C], T[D]]) => void
  ): void;
  <A extends keyof T, B extends keyof T, C extends keyof T>(
    a: [A, B, C], f: (a: [T[A], T[B], T[C]]) => void
  ): void;
  <A extends keyof T, B extends keyof T>(
    a: [A, B], f: (a: [T[A], T[B]]) => void
  ): void;
  <A extends keyof T>(
    a: [A], f: (a: [T[A]]) => void
  ): void;
}

处理最大长度为 4 的元组。如果您愿意,可以随意添加更多重载。让我们看看它是否有效:

myFunction<MyInterface>()(
  ['foo'], 
  ([foo]) => null    // foo is a string
);

myFunction<MyInterface>()(
  ['abc'],  //  error, '"abc"' is not '"foo" | "bar" | "baz"'.
  ([abc]) => null // error, abc is implicitly any
);

myFunction<MyInterface>()(
  ['bar', 'baz'],
  ([bar, baz]) => null  // bar is number, baz is boolean[]
);

看起来不错!希望有所帮助;祝你好运。