次の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のこの厄介な指定を消費者が忘れずに行う必要があるからです。
私が最初に望むようにジェネリック型を指定して、消費者に親切にすることができる構文はありますか?
いいえ、コンシューマはA
、B
、および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
と同様に、名前付き値のタイプは名前付きタイプではありません。大きなものはclass
とenum
です:
class Class { public prop = 0; }
enum Enum { A, B }
ここで、typeClass
はClass
のinstanceのタイプであり、valueClass
はconstructorオブジェクトです。 typeof Class
はClass
ではありません:
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 Enum
はEnum
ではありません:
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
ブレグ。ごめんなさい。
要点をまとめると:
それがいくらか理にかなっていることを願っています。がんばろう。