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

Ben*_*n M 6 generics type-inference typescript typescript-generics typescript2.0

我有以下示例对象:

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 }
}
Run Code Online (Sandbox Code Playgroud)

接口看起来应该是这样的:

interface Foo {
  [key: string]: { default: T, fn: (val:T) => any }
}
Run Code Online (Sandbox Code Playgroud)

这当然不起作用,因为没有T定义.

所以我想这样做:

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

interface Foo {
  [key: string]: FooValue<?>
}
Run Code Online (Sandbox Code Playgroud)

但在那里我也被卡住了.因为我无法定义泛型类型FooValue.

如果我FooValue<any>当然使用,那么一切都输入为any.虽然这不起作用.

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

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

jca*_*alz 8

如何定义Foo<T>映射类型,如下所示:

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

type Foo<T> = {
  [K in keyof T]: FooValue<T[K]>
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,如果T是一些普通的对象类型{a: string, b: number, c: boolean},那么它Foo<T>是它的Foo-ized版本:{a: FooValue<string>, b: FooValue<number>, c: FooValue<boolean>}.现在你可以创建一个辅助函数,只有当它可以被推断Foo<T>为某种类型时才接受一个对象文字T:

function asFoo<T>(foo: Foo<T>): Foo<T> {
  return foo;
}
Run Code Online (Sandbox Code Playgroud)

此功能,因为打字稿编译器可以做到从映射类型推断,允许它推断TFoo<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>;}
Run Code Online (Sandbox Code Playgroud)

这是失败的:

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'.
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.祝好运!


更新:上面的代码假定您可以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
Run Code Online (Sandbox Code Playgroud)

这很有效.在这种情况下,asFoo()只需验证输入是否Foo<T>为某些输入T,但它不会将输出类型强制转换为a Foo<T>.根据您的使用情况,您可能更喜欢这种解决方案.祝你好运.