【问题标题】:Typed Generic Key Value Interface in TypescriptTypescript 中的类型化通用键值接口
【发布时间】:2018-09-08 19:09:40
【问题描述】:

我有以下示例对象:

let foo: Foo = {
  'key1': { default: 'foo', fn: (val:string) => val },
  'key2': { default: 42, fn: (val:number) => val },

  // this should throw an error, because type of default and fn don't match
  'key3': { default: true, fn: (val:string) => val }
}

界面应如下所示:

interface Foo {
  [key: string]: { default: T, fn: (val:T) => any }
}

这当然行不通,因为没有定义T

所以我想这样做:

interface FooValue<T> {
  default: T;
  fn: (val:T) => any;
}

interface Foo {
  [key: string]: FooValue<?>
}

但我也陷入了困境。因为我无法定义FooValue的泛型类型。

如果我使用FooValue&lt;any&gt;,那么当然所有内容都输入为any。虽然那行不通。

我要确保default的类型和fn的参数类型始终相同。

有什么解决办法吗?还是不能这样做?

【问题讨论】:

  • interface Foo&lt;T&gt; 呢?
  • 那我在做let foo: Foo&lt;???&gt; = {...}的时候要定义T。然后我会为每个键设置通用类型。这样所有FooValue 对象必须是同一类型。但正如您在示例对象中看到的那样:我希望每个键值都有不同的泛型类型。

标签: typescript generics type-inference typescript2.0 typescript-generics


【解决方案1】:

如何将Foo&lt;T&gt; 定义为mapped type,如下所示:

interface FooValue<T> {
  default: T;
  fn: (val: T) => any;
}

type Foo<T> = {
  [K in keyof T]: FooValue<T[K]>
}

在这种情况下,如果T 是一些普通的对象类型,例如{a: string, b: number, c: boolean},那么Foo&lt;T&gt; 就是它的Foo 化版本:{a: FooValue&lt;string&gt;, b: FooValue&lt;number&gt;, c: FooValue&lt;boolean&gt;}。现在,您可以创建一个辅助函数,该函数仅在可以将某个类型 T 推断为 Foo&lt;T&gt; 时才接受对象文字:

function asFoo<T>(foo: Foo<T>): Foo<T> {
  return foo;
}

这个函数之所以有效,是因为 TypeScript 编译器可以执行 inference from mapped types,允许它从 Foo&lt;T&gt; 推断出 T。这是它的工作原理:

let foo = asFoo({
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val }
});
// inferred as { key1: FooValue<string>; key2: FooValue<number>;}

这是失败的:

let badFoo = asFoo(
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val },
  key3: { default: true, fn: (val: string) => val }
}); 
// error! Types of property 'key3' are incompatible. 
// Type 'boolean' is not assignable to type 'string'.

希望对您有所帮助。祝你好运!


更新:上面的代码假设您可以将foo.key1.fn('abc') 推断为类型any,因为FooValue&lt;string&gt;['fn'] 被定义为返回any 的函数。它有点忘记了原始对象文字的输出类型。如果你想让foo记住它的属性fn 方法的返回类型,你可以做这个稍微不同的辅助函数:

function asFoo<T, F>(foo: F & Foo<T>): F {
  return foo;
}

let foo = asFoo({
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val },
  // next line would cause error
  // key3: { default: true, fn: (val: string)=>val} 
})

const key1fnOut = foo.key1.fn('s') // known to be string
const key2fnOut = foo.key2.fn(123) // known to be number

这行得通。在这种情况下,asFoo() 只是验证某些T 的输入是Foo&lt;T&gt;,但它不会将输出类型强制为Foo&lt;T&gt;。根据您的用例,您可能更喜欢此解决方案而不是其他解决方案。再次祝你好运。

【讨论】:

  • 谢谢!很棒的帮助!我猜这个辅助函数就是我所需要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-04
  • 2016-04-05
  • 1970-01-01
  • 2021-06-28
  • 1970-01-01
  • 2020-05-04
  • 2022-11-29
相关资源
最近更新 更多