【问题标题】:Typescript Generics with ordered array / tuples带有有序数组/元组的打字稿泛型
【发布时间】:2018-04-15 22:29:57
【问题描述】:

我有一个函数,它需要 2 个参数:

  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:如何实现?

【问题讨论】:

  • 您尝试用于“内部函数”参数的语法称为array destructuring,它是 javascript 的一部分,并非特定于 typescript,您在 [] 中使用的名称确实如此没关系。数组元素在位置上与您选择的任何名称相关联。

标签: typescript generics generic-list


【解决方案1】:

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

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

泛型类型参数T 需要由调用者显式指定,但函数参数也需要是泛型的(例如A extends keyof T)。您不能拥有带有 &lt;T, A, B&gt; 之类的参数的函数,其中 T 需要手动指定,但 AB 需要从参数中推断出来。你基本上从类型参数推断中得到all or nothing。解决此问题的一种方法是使用curried function,其中myFunction&lt;T&gt;() 返回另一个以&lt;A, B&gt; 作为类型参数的函数。也就是说,改变这个:

// 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&lt;MyInterface&gt;() 中指定 MyInterface,并允许编译器在后续调用中推断 AB

tuples 很难通用:

TypeScript 缺少variadic kinds,因此实际上不可能为许多作用于“任意长度的元组”的函数提供单一签名。您最终需要通过提供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

只要你能站立。

把它放在一起:

所以,让我们像这样进行 curry 和重载:

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[]
);

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

【讨论】:

    猜你喜欢
    • 2021-07-26
    • 2020-10-02
    • 1970-01-01
    • 2021-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-05
    • 1970-01-01
    相关资源
    最近更新 更多