web-dev-qa-db-ja.com

typescriptでenumキーをユニオン文字列として取得するジェネリック型?

次のTypeScript列挙型を検討してください。

enum MyEnum { A, B, C };

その列挙のキーの結合された文字列である別のタイプが必要な場合は、次のことができます:

type MyEnumKeysAsStrings = keyof typeof MyEnum;  // "A" | "B" | "C"

これは非常に便利です。

今、私はこのように列挙型で普遍的に動作するジェネリック型を作成したいので、代わりに言うことができます:

type MyEnumKeysAsStrings = AnyEnumKeysAsStrings<MyEnum>;

そのための正しい構文は次のようになります。

type AnyEnumKeysAsStrings<TEnum> = keyof typeof TEnum; // TS Error: 'TEnum' only refers to a type, but is being used as a value here.

ただし、コンパイルエラーが発生します。「 'TEnum'は型のみを参照しますが、ここでは値として使用されています。」

これは予想外で悲しいことです。ジェネリックの宣言の右側からtypeofを削除し、それを特定の型の宣言のtypeパラメーターに追加することにより、次の方法で不完全に回避できます。

type AnyEnumAsUntypedKeys<TEnum> = keyof TEnum;
type MyEnumKeysAsStrings = AnyEnumAsUntypedKeys<typeof MyEnum>; // works, but not kind to consumer.  Ick.

ただし、この回避策は好きではありません。というのは、ジェネリックでtypeofのこの厄介な指定を消費者が忘れずに行う必要があるからです。

私が最初に望むようにジェネリック型を指定して、消費者に親切にすることができる構文はありますか?

15
Stephan G

いいえ、コンシューマはAB、およびCのキーを持つオブジェクトを参照するためにtypeof MyEnumを使用する必要があります。


長い説明、おそらくすでに知っているもののいくつか

おそらくご存知のように、TypeScriptは静的な型システムをJavaScriptに追加し、その型システムはコードがトランスコンパイルされると消去されます。 TypeScriptの構文は、一部の式とステートメントが実行時に存在するvaluesを参照し、他の式とステートメントがtypesのみを参照するようなものです設計/コンパイル時に。値は型を持っていますが、それ自体は型ではありません。重要なのは、コンパイラーが値を予期し、可能であれば値として検出した式を解釈するコード内の場所、およびコンパイラーが型を予期し、可能であれば式を検出した式を解釈する他の場所があることです。

式が値と型の両方として解釈される可能性がある場合、コンパイラは気にしませんし、混乱しません。インスタンス、次のコードのnullの2つのフレーバー:

let maybeString: string | null = null;

nullの最初のインスタンスはタイプであり、2番目は値です。また、問題ありません

let Foo = {a: 0};
type Foo = {b: string};   

ここで、最初のFooは名前付き値であり、2番目のFooは名前付きタイプです。値の型Foo{a: number}であり、型Foo{b: string}であることに注意してください。それらは同じではありません。

typeof演算子でさえ、二重の寿命をもたらします。 typeof xは常にxが値であることを期待しますが、typeof x自体はコンテキストに応じて値またはタイプになります。

let bar = {a: 0};
let TypeofBar = typeof bar; // the value "object"
type TypeofBar = typeof bar; // the type {a: number}

let TypeofBar = typeof bar;はJavaScriptに到達し、実行時に JavaScript typeof演算子 を使用して文字列を生成します。しかし、type TypeofBar = typeof bar;消去され、 TypeScript type query operator を使用して、TypeScriptがbarという名前の値に割り当てた静的型を調べています。


現在、名前を導入するTypeScriptのほとんどの言語構成体は、名前付き値または名前付きタイプのいずれかを作成します。名前付き値の紹介を次に示します。

const value1 = 1;
let value2 = 2;
var value3 = 3;
function value4() {}

そして、ここに名前付き型のいくつかの紹介があります:

interface Type1 {}
type Type2 = string;

しかし、名前付きの値の両方を作成するboth宣言がいくつかあります名前付きタイプ、および上記のFooと同様に、名前付き値のタイプは名前付きタイプではありません。大きなものはclassenumです:

class Class { public prop = 0; }
enum Enum { A, B }

ここで、typeClassClassinstanceのタイプであり、valueClassconstructorオブジェクトです。 typeof ClassClassではありません:

const instance = new Class();  // value instance has type (Class)
// type (Class) is essentially the same as {prop: number};

const ctor = Class; // value ctor has type (typeof Class)
// type (typeof Class) is essentially the same as new() => Class;

また、typeEnumは、列挙のelementのタイプです。各要素の型の結合。 valueEnumは、キーがAおよびBであるobjectであり、そのプロパティは列挙の要素です。 typeof EnumEnumではありません:

const element = Math.random() < 0.5 ? Enum.A : Enum.B; 
// value element has type (Enum)
// type (Enum) is essentially the same as Enum.A | Enum.B
//  which is a subtype of (0 | 1)

const enumObject = Enum;
// value enumObject has type (typeof Enum)
// type (typeof Enum) is essentially the same as {A: Enum.A; B: Enum.B}
//  which is a subtype of {A:0, B:1}

今あなたの質問にバックアップ方法。次のように機能する型演算子を作成します。

type KeysOfEnum = EnumKeysAsStrings<Enum>;  // "A" | "B"

ここで、typeEnumを入れ、objectEnum outのキーを取得します。しかし、上記のように、Enum型はオブジェクトEnumと同じではありません。残念ながら、型は値について何も知りません。次のように言っています:

type KeysOfEnum = EnumKeysAsString<0 | 1>; // "A" | "B"

明らかにそのように書くと、タイプ0 | 1を生成するタイプ"A" | "B"に対してできることは何もないことがわかります。動作させるには、マッピングを知っている型を渡す必要があります。そしてそのタイプはtypeof Enum...です.

type KeysOfEnum = EnumKeysAsStrings<typeof Enum>; 

のようなものです

type KeysOfEnum = EnumKeysAsString<{A:0, B:1}>; // "A" | "B"

whichisis ... if type EnumKeysAsString<T> = keyof T


そのため、コンシューマーにtypeof Enumを指定させることに固執しています。回避策はありますか?さて、関数などの値を使用するものを使用できますか?

 function enumKeysAsString<TEnum>(theEnum: TEnum): keyof TEnum {
   // eliminate numeric keys
   const keys = Object.keys(theEnum).filter(x => 
     (+x)+"" !== x) as (keyof TEnum)[];
   // return some random key
   return keys[Math.floor(Math.random()*keys.length)]; 
 }

その後、あなたは呼び出すことができます

 const someKey = enumKeysAsString(Enum);

someKeyのタイプは"A" | "B"になります。ええ、しかし、それをtypeとして使用するには、クエリする必要があります:

 type KeysOfEnum = typeof someKey;

これはtypeofを再度使用することを強制し、特にこれを行うことができないため、ソリューションよりもさらに冗長です。

 type KeysOfEnum = typeof enumKeysAsString(Enum); // error

ブレグ。ごめんなさい。


要点をまとめると:

  • これIS NOT POSSIBLE;
  • タイプと値BLAH BLAH;
  • まだ不可能です。
  • ごめんなさい。

それがいくらか理にかなっていることを願っています。がんばろう。

35
jcalz