TypeScriptによる型安全なSelect
背景
とあるWebサイトにおいて、サイト内の設定によって表示を変えるような実装が必要な場合、ユーザに表示する部分(一般的にlabel
と言われる)と内部で扱う固有の値(一般的にid
, key
, name
など)を型安全に実装したいとします。
例として、多種多様な”性別”の分だけ項目がある場合のSelect
の実装を考えます。
id
を表現する
値固有のFacebookには58個の性別オプションがあるそうですが、わかりやすくするために、5種類程度に抑えて表現します。
const GenderBase = ["cisgender_female", "cisgender_male", "trans_female", "trans_male", "other"] as const;
ポイントはas const
によりGenderBase
の型は値と等しいタプル型になります。
id
を型として扱う
タプル型の値を型として扱うためには、Union型が適しています。
type Gender = typeof GenderBase[number];
これによって、”GenderBase
が持つ値のうちいずれか”という型を定義することができます。
例えば以下のコードはエラーになります。
// Gender型に"neither"は含まれないためエラーになります。
const gender: Gender = "neither";
label
を定義する。
labelの特徴は、id
と1対1で対応している点です。これをデータとして表現する際には、前述のid
の値すべてを漏れなく満たすよう注意する必要があります。
const GenderLabel = {
cisgender_female: "女性",
cisgender_male: "男性",
trans_female: "性自認が女性の生物学的男性",
trans_male: "性自認が男性の生物学的女性",
other: "その他",
} as const satisfies Record<Gender, string>;
この実装のポイントはsatisfies
を使用することで、other
などのキーが不足していたり、余分なキーが存在していたりする場合にエラーとなります。
活用
GenderBase
を配列として表現することで次のような活用方法や恩恵があります。
<Select
option={GenderBase.map((value) => {
return {
name: value,
label: GenderLabel[value]
}
})}
{...props}
/>
上記のコードはSelect
要素にoption
を追加する際の一例ですが、GenderBase
を参照することで、漏れなくoption
を追加することができます。
また、TypeScriptのオブジェクトの値は配列のように参照することができるため、GenderLabel["cisgender_female"]
のようにすることで対になるlabel
を参照することができます。
さらに、GenderLabel
はsatisfies
によって縛られているため、新しい性別を追加しようとした際に、GenderBase
の配列へ値を追加するだけで、修正が必要な箇所でコンパイルの段階でエラーとすることができます。
Discussion