打字稿中键入的通用键值接口

时间:2018-03-29 18:12:41

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

我有以下示例对象:

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<any>,那么当然所有内容都输入为any。虽然这不起作用。

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

有什么解决方案吗?或者不能这样做?

1 个答案:

答案 0 :(得分:5)

如何将Foo<T>定义为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<T>是其Foo版本的版本:{a: FooValue<string>, b: FooValue<number>, c: FooValue<boolean>} 。现在,您可以创建一个辅助函数,只有在某些类型Foo<T>可以推断为T时才接受对象文字:

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

此函数有效,因为TypeScript编译器可以执行inference from mapped types,允许它从T推断出Foo<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<string>['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()只会验证某些Foo<T>的输入是T,但它不会将输出类型强制转换为Foo<T>。根据您的使用情况,您可能更喜欢这种解决方案。祝你好运。