【TypeScript】特定の値のときだけoptionalプロパティがrequiredになる型定義
TypeScriptで特定のプロパティの値が選ばれたときだけオプショナルなプロパティを必須にする方法です。
現象
TypeScriptで型定義をしていると、以下のような型定義をしたくなることがあります。
gender
プロパティの値がother
のときだけ、genderText
プロパティが必須で、それ以外の場合はgenderText
プロパティは不要
単純に考えると以下のような型定義になると思います。
type User = {
name: string;
age: number;
gender: 'man' | 'woman' | 'other'
genderText?: string;
}
上記型定義では、gender
がother
のときでもgenderText
が存在するかどうかをTypeScriptコンパイラが判断できないため、以下のように値が存在する前提でコードを書くとundefined
の可能性がありますと怒られてしまいます。
type User = {
name: string;
age: number;
gender: 'man' | 'woman' | 'other'
genderText?: string;
}
const user: User = {
name: 'Taro',
age: 30,
gender: 'other',
genderText: 'heterosexual'
}
console.log(user.genderText.toUpperCase()) // ERROR: Object is possibly 'undefined'.(2532)
解決策
結論
以下のように定義すると、gender
がother
のときはgenderText
が存在するとTypeScriptコンパイラが判断するため、エラーが出ません。
type BasicUser = {
name: string;
age: number;
}
type BasicGenderUser = BasicUser & {
gender: 'man' | 'woman',
}
type OtherGenderUser = BasicUser & {
gender: 'other',
genderText: string,
}
type User = BasicGenderUser | OtherGenderUser
const user: User = {
name: 'Taro',
age: 30,
gender: 'other',
genderText: 'heterosexual'
}
console.log(user.genderText.toUpperCase()) // エラーが出ない
解説
まずはgender
の値に限らず共通なプロパティをBasicUser
型として定義します。
type BasicUser = {
name: string;
age: number;
}
次に、条件分岐させたい型をそれぞれ定義します。
gender
の値に応じてgendetText
の有無が確定するので、それぞれgenderText
が存在するときと存在しないときで型定義します。
ここで、BasicUser
を継承することで最終的に作りたいUser
型に必要なプロパティをもたせます。
type BasicUser = {
name: string;
age: number;
}
+ type BasicGenderUser = BasicUser & {
+ gender: 'man' | 'woman',
+ }
+ type OtherGenderUser = BasicUser & {
+ gender: 'other',
+ genderText: string,
+ }
最後に、それぞれのユニオン型としてUser
型を定義することで、gender
がother
のときはgenderText
を持っている(OtherGenderUser
)型として認識され、gender
がman
もしくはwoman
のときはgenderText
を持っていない(BasicGenderUser
)型として認識されるようになります。
type BasicUser = {
name: string;
age: number;
}
type BasicGenderUser = BasicUser & {
gender: 'man' | 'woman',
}
type OtherGenderUser = BasicUser & {
gender: 'other',
genderText: string,
}
+ type User = BasicGenderUser | OtherGenderUser
おまけ
gender
プロパティとして取り得る値(man
| woman
| other
)を定義したい場合は、以下のようにすることできれいに書けます。
type BasicUser = {
name: string;
age: number;
}
type BasicGenderUser = BasicUser & {
gender: 'man' | 'woman',
}
type OtherGenderUser = BasicUser & {
gender: 'other',
genderText: string,
}
type User = BasicGenderUser | OtherGenderUser
+ type Gender = BasicGenderUser['gender'] | OtherGenderUser['gender'] // type Gender = "man" | "woman" | "other"
Discussion