TypeScriptでUnion型の判別
実際に案件でUnion型を判別していた処理をリファクタリングした時の内容を共有します。
もともとのコードは以下のような内容です。
type Form = CheckForm | FreeTextForm;
interface BaseForm {
id: number;
type: 'checkbox' | 'freeText';
description: string;
}
interface CheckForm extends BaseForm {
options: Options[];
}
interface FreeTextForm extends BaseForm {
maxLength: number;
}
const form: Form = {
id: 1,
type: 'checkbox',
description: 'select only one',
options: [{key: 1,value: '1'},{key: 2,value: '2'}]
}
function findOptionIndex(form, key) {
// CheckFormのときだけ処理をしたい
if (form.type === 'checkbox') {
const f = form as CheckForm; // form.optionsを参照できるように型を指定している
return form.options.findIndex((opt) => opt.key === key);
} else {
return -1;
}
}
Formは複数の型を結合したUnion型のため、form.options
を参照しようとするとoptions
をメンバとして持っていない可能性があると怒られてしまいます。
そのためas
を使ってform
の型をCheckForm
として扱うように指定していました。
しかしこの記述をするにはいちいち変数にオブジェクトを入れ直さないといけないのであまりよろしくないです。
なので、まず特定の条件をクリアした場合にどういう型として扱うか指定することができるユーザー定義の型ガードというので書き直してみました。
const isCheckForm = (form: Form): form is CheckForm => {
return form.type === 'checkbox';
}
function findOptionIndex(form: Form, key: number) {
if (isCheckForm(form)) {
return form.options.findIndex((opt) => opt.key === key);
} else {
return -1;
}
}
上記のように関数の返り値をarg is TypeName
とすることで、関数の返り値がtrue
だった場合に引数にいれたarg
をTypeName
で指定した型のオブジェクトとして扱うことができます。
これで可読性もだいぶ良くなり型の予測もしやすくなりました。
ただ一つ問題があり、この方法だとis
の後ろにどんな型でも指定できるので、開発者が間違った型を指定するとTypeScriptがエラーを検知できなくなるリスクがあります。
Union型のメンバが持つリテラル型のプロパティを元にUnion型のメンバを判別できるらしく、最終的に先ほどのリファクタリングは諦めて、CheckForm
とFreeTextForm
のtype
プロパティに文字列を指定することで解決しました。
内容は以下のようになりました。
type Form = CheckForm | FreeTextForm;
interface BaseForm {
id: number;
type: 'checkbox' | 'freeText';
description: string;
}
interface CheckForm extends BaseForm {
type: 'checkbox'; // 判別のためにリテラル型のプロパティを含める
options: Options[];
}
interface FreeTextForm extends BaseForm {
type: 'freeText'; // 判別のためにリテラル型のプロパティを含める
maxLength: number;
}
const form: Form = {
id: 1,
type: 'checkbox',
description: 'select only one',
options: [{key: 1,value: '1'},{key: 2,value: '2'}]
}
function findOptionIndex(form: Form, key: number) {
// CheckFormのときだけ処理をしたい
if (form.type === 'checkbox') { // CheckFormのtypeプロパティと一致しているため、CheckFormと認識される
return form.options.findIndex((opt) => opt.key === key);
} else {
return -1;
}
}
参考
https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard
https://typescript-jp.gitbook.io/deep-dive/type-system/discriminated-unions
Author And Source
この問題について(TypeScriptでUnion型の判別), 我々は、より多くの情報をここで見つけました https://qiita.com/t-hsmt/items/f29bcb8a161181ce39f1著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .