web-dev-qa-db-ja.com

Typescriptの型付き総称キー値インターフェース

次のサンプルオブジェクトがあります。

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のパラメータータイプが常に同じであることを確認したいと思います。

何か解決策はありますか?それともできないのですか?

6
Benjamin M

次のように、_Foo<T>_を マップされたタイプ として定義するのはどうでしょうか。

_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>_}です。これで、一部のタイプTの_Foo<T>_として推論できる場合にのみ、オブジェクトリテラルを受け入れるヘルパー関数を作成できます。

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

TypeScriptコンパイラが マップされたタイプからの推論 を実行できるため、この関数が機能し、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'.
_

お役に立てば幸いです。幸運を!


更新:上記のコードは、_FooValue<string>['fn']_がanyを返す関数として定義されているため、foo.key1.fn('abc')が型anyとして推論されても問題ないと想定しています。元のオブジェクトリテラルからの出力タイプを忘れてしまいます。プロパティのfooメソッドの戻り値の型をfnrememberさせたい場合は、これをわずかに異なる方法で行うことができますヘルパー関数:

_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<T>_であることを確認するだけですが、出力タイプを_Foo<T>_に強制変換しません。ユースケースによっては、このソリューションを他のソリューションよりも好む場合があります。頑張ってください。

12
jcalz