そんなん型定義ちゃんとしてれば、チェックなんかいらんのでは?
まぁ、ごもっともなお話なんですが。。。
今回のお話としては、
画面上の定義ではstringで定義したいけど、
openapi-generatorとかで作られるやつだとnumber定義になってて、
APIのパラメータに設定したいがために、クソ長い if や switch は書くたくないというところから、
汎用的なチェック用のやつ作れば幸せになれるんじゃないかという安易な考えで思いついたソースのご紹介。
※本ソースによる不具合や被害などは一切受け付けませんので、用法・用量をしっかり守ってね!
そんなわけでまずは openapi-generator で作られるTypeのサンプル
// ステータスの値は数値で管理するよ
export type AppStatus = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export const AppStatus = {
NUMBER_1: 1 as AppStatus,
NUMBER_2: 2 as AppStatus,
NUMBER_3: 3 as AppStatus,
NUMBER_4: 4 as AppStatus,
NUMBER_5: 5 as AppStatus,
NUMBER_6: 6 as AppStatus,
NUMBER_7: 7 as AppStatus,
NUMBER_8: 8 as AppStatus,
};
画面側のサンプル
// セレクトボックスの値を格納する変数(string型)
public selectValue = "";
// セレクトボックスの値が変わったときに呼ばれるイベント
const changeSelect = ($event) => {
console.debug($event.detail.value);
// ここで「$event.detail.value」の値が、
// AppsStatusの何になるかをチェックしたいよー
}
画面上でinputとかselectのvalueって、string型で受け取れちゃうんだけど、
その値がTypeに存在してるとか、Typeの別名(Enumみたいなの)に変換したいとなった時って、
単純に Number.parseInt() で変換した値ではビルド時のエラーがガンガン出てしまうため、
以下のようなクソ長ったらしい条件を書かないと駄目だったりするわけで。。。
let appStatus:AppStatus;
switch($event.detail.value){
case "1":
appStatus = AppStatus.NUMBER_1;
break;
case "2":
appStatus = AppStatus.NUMBER_2;
break;
case "3":
appStatus = AppStatus.NUMBER_3;
break;
// 以下省略
}
またたとえnumberで定義していたとして、きちんとTypeと併用したもので定義しないとコレはコレでエラーが出るという、型付言語の定めのような言語仕様というね。
let num:number = 3;
// VSCode上では以下のようなエラーが出てしまう
// 型 'number' を型 'AppStatus' に割り当てることはできません。ts(2322)
let appStatus:AppStatus = num;
そんなニッチなお悩みを遠回りに解決
まぁまずはチェック処理からご紹介
// チェック用関数
// T:Type
// U:Typeの型
export function isMatchType<T extends U, U>(
val: U,
matchFunc: (val: U) => boolean
): val is T {
return matchFunc(val);
}
val is T というのが重要で、この関数でTrueが返ってくるということは「valはTに存在してるよ」って扱いになり、エラーが一気に解消される(型ガード)
でも結局は自分でチェック用のFunction書かないと駄目じゃんとお思いかと思いますが、
とりあえず利用例をチェケラ
const selectVal = $event.detail.value;
// AppStatusに存在する値ならTrueを返すやつ
const isMatch: boolean = isMatchType<AppStatus, number>(Number.parseInt(selectVal), (val) =>
Object.keys(AppStatus)
.map((k) => AppStatus[k])
.some((v) => val)
)
処理のフローとしては、
Object.keysで「NUMBER_1」や「NUMBER_2」を取得し、
そのKey情報を使ってTypeで定義されている実際の値の配列にし、
その実際の値の配列に引数で渡された値があるかチェックしてるだけ。
ね、簡単でしょ?
で、上記処理と合わせて、Type変換用の関数も紹介
export function toAppStatus(val: string): AppStatus {
if (val && val.length > 0) {
const num = Number.parseInt(val);
return isMatchType<AppStatus, number>(num, (val) =>
Object.keys(AppStatus)
.map((k) => AppStatus[k])
.some((v) => val)
)
? num
: AppStatus.NUMBER_2; // マッチしなかったときのデフォルト値
} else {
// マッチしなかったときのデフォルト値
return AppStatus.NUMBER_2;
}
}
変換用の関数を汎用的に作れたらなと思ったんですが、ちょっと動くかどうか怪しいのでざっくり雛形だけ
export function toNumberTypeParamString<T extends number>(tEnum, val: string, defaultValue?: T): T{
if (val && val.length > 0) {
const num = Number.parseInt(val);
return isMatchType<T, number>(num, (val) =>
Object.keys(tEnum)
.map((k) => tEnum[k])
.some((v) => val)
)
? num
: defaultValue;
} else {
return defaultValue;
}
};
// 利用サンプル
export type AppStatus = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
export const AppStatus = {
NUMBER_1: 1 as AppStatus,
NUMBER_2: 2 as AppStatus,
NUMBER_3: 3 as AppStatus,
NUMBER_4: 4 as AppStatus,
NUMBER_5: 5 as AppStatus,
NUMBER_6: 6 as AppStatus,
NUMBER_7: 7 as AppStatus,
NUMBER_8: 8 as AppStatus,
};
const appStatus:AppStatus = toNumberTypeParamString<AppStatus>(AppStatus, '1', AppStatus.NUMBER_1);
使ってるフレームワークやライブラリ、ツールなんかによってType縛りプレイが発生してる時とかに使えればと。