4歳娘「パパ、オブジェクトから型を作って?」


ある休日

娘(4歳)「パパ、ジャンケンしよ!?」

ワイ「おお、娘ちゃん」
ワイ「もうジャンケンができるようになったんか!」
ワイ「まだ関数を書くことくらいしかできんと思ってたわ〜」

よめ太郎「順番どうなってんねん」

ワイ「ほな、ジャンケンしようや!」

さっそく

娘「じゃあ、さっそくコードを見ていきたいんだけど・・・」

ワイ「ファッ!?
ワイ「コード!?

janken.ts
const hands = {
  gu: "グー",
  choki: "チョキ",
  pa: "パー"
};

娘「まず↑こんなオブジェクトを定義してみたの」

ワイ「お、おお」
ワイ「ジャンケンの手やからhandsね」

娘「そう」
娘「それで」
娘「このhandsっていうオブジェクトから、を作りたいの」

ワイ「オブジェクトから、型を、作る・・・?」

娘「"グー""チョキ""パー"っていう文字列しか許可しない・・・」
娘「Hand型っていうのを定義したいの」

ワイ「なるほどな」
ワイ「それなら・・・」

janken.ts
type Hand = "グー" | "チョキ" | "パー";

ワイ「↑これでええやん」
ワイ「このHand型"グー""チョキ""パー"っていう文字列しか許可せえへんで!」
ワイ「union型いうやつやな」
ワイ「これで、間違って不正な値を代入せんようにできるで!」
ワイ「試しに"ピャー"という文字列を代入しようとすると・・・」

ワイ「ほら、VSCodeくんが赤い波線を表示してくれとる」
ワイ「エラーっちゅうことや」

娘「うん」
娘「でも、それは自分でunion型を書いてるだけでしょ?」
娘「handsっていうオブジェクトを元に、自動でunion型を作りたいの
娘「このオブジェクトの持つ値は"グー""チョキ""パー"って分かり切ってるのに」
娘「わざわざ自分で"グー" | "チョキ" | "パー"っていうunion型を再定義したくないの」

ワイ「ハハハ」
ワイ「それは、残念ながら無理なご相談や」
ワイ「それに、オブジェクトの中身が書き換えられる可能性もあるやん」
ワイ「いくらconstで定義しても、オブジェクトは不変ではないんやから」

娘「でも前に、ハスケル子おねえちゃんができるって言ってたよ
娘「オブジェクトからunion型を生成できるって」

ワイ「(ぐぬぬ)」
ワイ「ええと・・・」

娘「パパ、早く教えて?」
娘「私の稼働がもったいないの」

ワイ「稼働て・・・」
ワイ「(なんか、ワイも前にハスケル子ちゃんから教えてもらった気がするな・・・)」

必死に思い出す

ワイ「せや!」
ワイ「確か、順序としては・・・」

  1. まずkey名から"gu" | "choki" | "pa"というunion型を作る。
  2. それを使って"グー" | "チョキ" | "パー"というunion型を作る。

ワイ「↑こんな感じのはずや」
ワイ「あと・・・」

  • keyof演算子
  • typeof演算子

ワイ「↑この2つの演算子を使う、って言ってた気がするで」
ワイ「あと、まずは」
ワイ「handsオブジェクトの最後んところas constをつけるんや」

janken.ts
const hands = {
  gu: "グー",
  choki: "チョキ",
  pa: "パー"
} as const; // <- 追加

ワイ「↑こうやな」
ワイ「これでhandsオブジェクトは、中身を書き換えたりできない・・・」
ワイ「readonlyなオブジェクトになったんや」

娘「へぇ〜」

ワイ「このオブジェクトの持つ値は"グー""チョキ""パー"だって、確定してないとアカンからな」

娘「なるほどね」

ワイ「試しにVSCodehandsオブジェクトにマウスを乗せて」
ワイ「handsオブジェクトの型を確認してみ?」

娘「うん!」

娘「ほんとだ!」
娘「全部のkeyがreadonlyになってる!」

ワイ「これで、handsオブジェクトのkeyは常にguchokipaであること」
ワイ「valueは常に"グー""チョキ""パー"であること」
ワイ「それが型によって担保されたで!」
ワイ「ほんまの意味で定数になったってことやな」

娘「だからas constなんだね!」

typeofで型情報を見る

娘「パパ、それで」
娘「まずkey名から"gu" | "choki" | "pa"っていうunion型を作るんだっけ?」
娘「どうやってやるの?」

ワイ「まずな、handsオブジェクトの型情報があったやろ?」

娘「さっきVSCodeで確認したやつね」

ワイ「せや」
ワイ「それに別名を付けてやるんや」

janken.ts
type Hoge = typeof hands;

ワイ「↑こうや」

娘「へぇ〜」
娘「typeof handsっていうのが」
娘「handsオブジェクトの型情報、ってことなんだ!」
娘「それにHogeっていう別名を付けたんだね」

ワイ「せや」
ワイ「またVSCodeでマウスを乗せて確認してみ?」
ワイ「Hoge型に何が入っとるかを見るんや」

娘「うん!」

娘「ほんとだ」
娘「さっきのhandsオブジェクトの型情報と同じだ」

ワイ「そういう感じや」

keyofで、key名からunion型を定義する

ワイ「次はkeyofを使って」
ワイ「Hoge型に存在するkey名からunion型を作るんや」

janken.ts
type Keys = keyof Hoge;

ワイ「↑こうやな」
ワイ「マウスを乗せてKeys型の中身を見てみ?」

娘「あ、"gu" | "choki" | "pa"っていうunion型が出来てる」

ワイ「せや」
ワイ「こいつを元に、最終目的である・・・」
ワイ「"グー" | "チョキ" | "パー"というunion型を作るんや!」

娘「おお〜」
娘「今日のパパ、なんか凄い!」

またtypeofで型情報を見る

ワイ「次にやることは↓こんなイメージや」

janken.ts
type Hand = typeof hands["gu"];

娘「typeofhands["gu"]の型を見て」
娘「その型にHandっていう別名を付けたんだね」

ワイ「せや」
ワイ「このHand型の中身は何やと思う?」

娘「ええと」

janken.ts
const hands = {
  gu: "グー",
  choki: "チョキ",
  pa: "パー"
} as const;

娘「まず、handsオブジェクトの中身は↑こうだから」
娘「hands["gu"]の中身は"グー"でしょ?」
娘「だから、要はtypeof "グー"は何になるか、的な話でしょ?」
娘「じゃあ、型もそのまま"グー"なんじゃない?」

ワイ「正解や」

娘「念のためマウス乗せて見てみよ」

娘「ほんとだ。Hand型の中身は"グー"だ」

ワイ「せやせや」
ワイ「ほな、これはどうや?」

janken.ts
type Hand = typeof hands["gu" | "choki"];

娘「ええと」
娘「hands["gu" | "choki"]だから」
娘「値としては"グー""チョキ"が入ってるイメージだよね」
娘「じゃあ、型は"グー" | "チョキ"かな?」

ワイ「さすが、正解や」

娘「ほんとだ」
娘「ってことは・・・」

janken.ts
type Hand = typeof hands["gu" | "choki" | "pa"];

娘「↑こうすれば・・・」
娘「"グー" | "チョキ" | "パー"っていうunion型が作れるんだね!?」
娘「私の欲しかったやつ!」

ワイ「せや!」

娘「さっきkeyofで作ったKeys型が」
娘「"gu" | "choki" | "pa"っていうunion型だったから」
娘「それを使うんだね!」

ワイ「せや」

娘「つまり・・・」

janken.ts
type Hand = typeof hands[Keys];

娘「↑こうだね!?」

ワイ「さぁ〜、どうやろね」

娘「VSCodeでマウスを乗せて覗いてみる!」

娘「できた!」
娘「handsオブジェクトからHand型ができた!」
娘「これで、分かり切った"グー" | "チョキ" | "パー"を書かなくても」
娘「オブジェクトの値から、自動っぽく型が作れる!」
娘「パパ、ありがとう!」

ワイ「グヘヘ」

娘「試しに不正な値を代入してみよっと!」

娘「あ、ちゃんとエラー扱いになって、波線が表示されてる!」
娘「これで、Hand型の変数には」
娘「"グー""チョキ""パー"しか代入できないね!」
娘「不正な値は代入できないね!」

ワイ「せや!」
ワイ「ちなみに、いちいち型エイリアスを作らなくても」

janken.ts
type Hand = typeof hands[keyof typeof hands];

ワイ「↑こう書いても同じことやで」
ワイ「確認のためにVSCodeで見てみると・・・」

ワイ「↑ほらな」

娘「ほんとだ!」

ワイ「せやから、全体のコードとしては・・・」

janken.ts
const hands = {
  gu: "グー",
  choki: "チョキ",
  pa: "パー"
} as const;

type Hand = typeof hands[keyof typeof hands];

ワイ「↑これだけや」

娘「オブジェクトを元にして型を定義する部分は」
娘「1行で書けちゃうんだね」

ワイ「せやで」

反復処理も可能

ワイ「元となるオブジェクトであるhandsが存在してる訳やから」
ワイ「Object.keys(hands)とかObject.values(hands)とかObject.entries(hands)みたいな」
ワイ「反復処理も可能やで!」

娘「いいね!」
娘「たまにそういうことしたい時あるもんね!」

よめ太郎「4歳児には無いやろ

でも、enumでよくない?

娘「でもさ・・・」
娘「これenumで良くない?」

janken.ts
enum Hands {
  gu = "グー",
  choki = "チョキ",
  pa = "パー"
}

娘「↑こんな感じで良くない?」
娘「enumでも反復処理はできるみたいだし」

ワイ「enumでもええねんけど」
ワイ「enumって何か、分かりにくいやん」

娘「そう?」

ワイ「enumというに対してObject.entries()とかそういう」
ワイ「オブジェクト用のメソッドが適用できるのが何か腑に落ちんし」
ワイ「TSからJSに変換されると訳わからんコードになってまうし」
ワイ「人類には早過ぎたんや」

よめ太郎「人類っていうかお前個人の問題やな」

娘「でもまあ、分からなくもないね」

まとめ

  • typeof演算子を使うと、型の情報を取得して別名をつけることができた。
  • keyof演算子を使うと、型のkey名からunion型を生成できた。
  • typeof obj[keyof typeof obj]とする事で、オブジェクトからunion型を生成できた。
  • 元となるオブジェクトが存在するため、Object.keys()Object.values()Object.entries()などの反復処理も可能。

娘「↑こんな感じだね!」

ワイ「せや!」

その日の夜

ワイ「はあ、今日は娘ちゃんの役に立てて嬉しいわ」
ワイ「ワイでも技術的なことで役に立てることがあるんやな」
ワイ「滅多にないけどな」
ワイ「・・・せや!」
ワイ「もう一回さっきのコードいじって復習しとこ」

janken.ts
type Hand = typeof hands["guu"];

ワイ「↑こう書くと、確か・・・」
ワイ「Hand型の中身は"グー"になるはずやったな」
ワイ「VSCodeで確認してみよ」

ワイ「ファッ!?
ワイ「アニーになっとるやないかい!」

よめ太郎「エニーや」
よめ太郎「アニーてそれ、ミュージカルやないかい」
よめ太郎「♪トゥモロー、トゥモロー言うてる場合か」

ワイ「そ、それより何で、型がanyに・・・」
ワイ「・・・分かった!」
ワイ「VSCodeのバグや!

よめ太郎「バグってんのはお前の脳みそや

ワイ「いや、これはワイが正しい気がする」

よめ太郎「タイポしてんねん
よめ太郎「"gu""guu"ってミスタイプしてんねん」

ワイ「ファッ!?

よめ太郎「お前のはTypeScriptやなくてタイポスクリプトや」

ワイ「ぐぬぬ」

よめ太郎「VSCodeはな」
よめ太郎「TypeScriptを作っとるMicrosoftはんが作ってんねや」
よめ太郎「何でお前の方が正しいと思えたねん

ワイ「テヘヘ・・・」
ワイ「ジョブズに怒られてまうわ」

よめ太郎「Microsoftはビル・ゲイツやろ・・・」
よめ太郎「エグいほど無知やな・・・」

ワイ「ぐぬぬ」
ワイ「いや・・・ワイはジョブズの方が好きやから・・・なんか・・・間違えてもうたわ」

よめ太郎「言い訳が苦し過ぎやろ」

ワイ「い、言い訳ちゃうわ」
ワイ「ほ、ほんまにジョブズが好きなんやワイは!」

よめ太郎「そうか」
よめ太郎「ほな、今から会わせたるわ・・・

〜おしまい〜

参考文献