1. ホーム
  2. typescript

[解決済み] 文字列ユニオンから文字列配列へ

2022-08-19 01:19:14

質問

このような文字列ユニオン型があります。

type Suit = 'hearts' | 'diamonds' | 'spades' | 'clubs';

この文字列結合で使用できるすべての値を型安全な方法で取得したいのです。しかし、インターフェースは主に設計時の構成であるため、私にできる最善の方法はこれです。

export const ALL_SUITS = getAllStringUnionValues<Suit>({
    hearts: 0,
    diamonds: 0,
    spades: 0,
    clubs: 0
});

export function getAllStringUnionValues<TStringUnion extends string>(valuesAsKeys: { [K in TStringUnion]: 0 }): TStringUnion[] {
    const result = Object.getOwnPropertyNames(valuesAsKeys);
    return result as any;
}

この関数は、各キーが文字列結合の要素であるオブジェクトを常に渡し、すべての要素が含まれることを確認し、すべての要素の文字列配列を返します。そのため、文字列結合が変更された場合、この関数の呼び出しも更新されないとコンパイル時にエラーになります。

しかし が問題です。 定数の型シグネチャが ALL_SUITS('hearts' | 'diamonds' | 'spades' | 'clubs')[] . 言い換えると、TypeScriptはこれを、すべての値を一度だけ含む配列ではなく、これらの値のいずれか、あるいは複数を含み、重複している可能性がある配列と考える。 ['hearts', 'diamonds', 'spades', 'clubs'] .

私が本当に欲しいのは、私の一般的な getAllStringUnionValues を返すように指定する方法です。 ['hearts', 'diamonds', 'spades', 'clubs'] .

どうすれば実現できますか? 一般的に でありながら DRY を使うことができますか?

どのように解決するのですか?

TypeScript 3.4以上の場合の回答

TypeScriptでユニオンをタプルに変換することは、少なくともうまく動作する方法ではありません。 ユニオンは 順不同 であり、タプルは本質的に 順序付き であるため、何とかできたとしても、結果として得られるタプルは予期せぬ振る舞いをする可能性があります。 参照 この回答 をご覧ください。このメソッドは実際に論理和からタプルを生成しますが たくさん の注意書きがあります。 また マイクロソフト/TypeScript#13298 を参照してください。また、Union-to-tuple 変換に関する機能要求が却下されましたが、議論と、なぜこれがサポートされないかについての正式な回答があります。

しかし、使用例によっては、この問題を逆転させることができるかもしれません。 タプル 型を明示的に指定し ユニオン を導出します。 これは比較的簡単です。

TypeScript 3.4以降では、TypeScript 3.1以降では const アサーション で、コンパイラにリテラルのタプルの型を推論するように指示します。 をリテラルタプルとして としてではなく、例えば string[] . これは値に対して可能な限り狭い型を推論する傾向があり、例えばすべてを readonly . だから、こうすればいいんです。

const ALL_SUITS = ['hearts', 'diamonds', 'spades', 'clubs'] as const;
type SuitTuple = typeof ALL_SUITS; // readonly ['hearts', 'diamonds', 'spades', 'clubs']
type Suit = SuitTuple[number];  // "hearts" | "diamonds" | "spades" | "clubs"

コードへのプレイグラウンドリンク


TypeScript 3.0から3.3までの回答

TypeScript 3.0から、TypeScriptで 自動的にタプル型を推論する . それがリリースされると tuple() という関数が必要になりますが、これは簡潔に次のように書くことができます。

export type Lit = string | number | boolean | undefined | null | void | {};
export const tuple = <T extends Lit[]>(...args: T) => args;

そして、このように使うことができます。

const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type


3.0以前のTypeScriptに対する回答

この回答を投稿してから、ライブラリに関数を追加してくれるなら、タプル型を推論する方法を見つけました。 関数をチェックアウトして tuple() tuple.ts .

これを使うと、以下のように書くことができ、繰り返しにならない。

const ALL_SUITS = tuple('hearts', 'diamonds', 'spades', 'clubs');
type SuitTuple = typeof ALL_SUITS;
type Suit = SuitTuple[number];  // union type


オリジナルの回答

あなたが望むものを得るための最も簡単な方法は、タプルの型を明示的に指定し、そこからユニオンを派生させることです。 はその方法を知らないのです。 . 例えば

type SuitTuple = ['hearts', 'diamonds', 'spades', 'clubs'];
const ALL_SUITS: SuitTuple = ['hearts', 'diamonds', 'spades', 'clubs']; // extra/missing would warn you
type Suit = SuitTuple[number];  // union type

リテラルを二度書き出していることに注意してください。 SuitTuple で型として、そして ALL_SUITS TypeScriptは現在のところ、このような繰り返しを避けるための素晴らしい方法がないことに気づくだろう。 タプルを推論する と指示することができず 決して はタプル型から実行時配列を生成します。

ここでの利点は、実行時にダミーオブジェクトのキー列挙を必要としないことです。 もちろん、まだ必要であれば、スートをキーにした型を構築することもできます。

const symbols: {[K in Suit]: string} = {
  hearts: '♥', 
  diamonds: '♦', 
  spades: '♠', 
  clubs: '♣'
}

お役に立てれば幸いです。