TypeScriptで巨大なオブジェクトに安全に扱う
業務でTypeScriptの型レベルプログラミングが活躍した話をします。
背景
とある案件で、200以上のプロパティーを持つ巨大なオブジェクトを扱う必要がありました。
200以上のプロパティーを分類するためのルールが見つけられない場合はお手上げですが、幸いにもプロパティー名はいくつかの命名ルールに従っており、その命名ルールごとにプロパティーを分類することができました。「分類できるなら正規化とか検討したほうがよくない?」と思いましたが、そこは大人の事情があります。
命名ルールのうちの1つは、次の4つの要素から成り立っていました。
- グループ名
- プロパティー名(正規化した場合に想定される名前)
- 英語 or 日本語
- 1 or 2 or 3
例としてグループ名がgroup1
、正規化していた場合のプロパティー名がfoo
,bar
だとすると、こんなイメージです。
type LargeObject = {
group1FooEn1: string;
group1FooEn2: string;
group1FooEn3: string;
group1FooJa1: string;
group1FooJa2: string;
group1FooJa3: string;
group1BarEn1: string;
group1BarEn2: string;
group1BarEn3: string;
group1BarJa1: string;
group1BarJa2: string;
group1BarJa3: string;
...
// 200以上のプロパティーが並ぶ
}
グループ名は同じ単語から始まるものもあったので、なんとか安全に扱いたいと考えました。
解決方法: 巨大なオブジェクトを型レベルで分割する
ある処理において、あるグループのプロパティーだけが必要だと分かっていれば、他のプロパティーにアクセスさせないよう型で制限を掛けることが出来ます。
巨大なオブジェクトからグループごとプロパティーを抽出するCreateGroupKey
というユーティリティー型を作りました。グループごとプロパティーを抽出できれば、あとは組み込み型のPick
を使えばOKです。
type LargeObject = {
group1FooEn1: string;
group1FooEn2: string;
...
group2BazEn1: string;
group2BazEn2: string;
...
// 200以上のプロパティーが並ぶ
}
type Lang = 'En' | 'Ja';
type Index = 1 | 2 | 3;
type CreateGroupKey<Group extends string> =
keyof LargeObject extends infer K
? K extends `${Group}${string}${Lang}${Index}`
? K
: never
: never;
type Group1Key = CreateGroupKey<'group1'>;
type Group1 = Pick<LargeObject, Group1Key>; // group1~ で始まるプロパティーのみ
type Group2Key = CreateGroupKey<'group2'>;
type Group2 = Pick<LargeObject, Group2Key>; // group2~ で始まるプロパティーのみ
こちらのプレイグラウンドで確認出来ます。
関数やUIコンポーネントのPropsにこれらのグループごとのプロパティーにのみアクセスできる型を使って特定のグループ外のプロパティーにアクセスするコードを書くと型エラーを発生させられるようになりました。めでたしめでたし。
ちなみに、一発でGroup型を作れるユーティリティー型も作れます。
type CreateGroup<Group extends string> =
Pick<
LargeObject,
keyof LargeObject extends infer K
? K extends `${Group}${string}${Lang}${Index}`
? K
: never
: never
>;
こちらのプレイグラウンドで確認出来ます。
一発で書けるとカッコいい感じもしますが、構造のネストが深いのでパット見で可読性が悪いと感じます。
また元の書き方は、ホバーすれば途中のロジックが合っているか確認できます。
しかし一発で書いた場合は途中の確認が出来ない点もマイナスです。
私にとって型レベルプログラミングは楽しいものですが、他のメンバーが楽しいと思う前に挫折しないよう、できるだけ配慮できるといいですね。
Discussion