ある休日
娘(4歳)「パパ、ジャンケンしよ!?」
ワイ「おお、娘ちゃん」
ワイ「もうジャンケンができるようになったんか!」
ワイ「まだ関数を書くことくらいしかできんと思ってたわ〜」
よめ太郎「順番どうなってんねん」
ワイ「ほな、ジャンケンしようや!」
さっそく
娘「じゃあ、さっそくコードを見ていきたいんだけど・・・」
ワイ「ファッ!?」
ワイ「コード!?」
const hands = {
gu: "グー",
choki: "チョキ",
pa: "パー"
};
娘「まず↑こんなオブジェクトを定義してみたの」
ワイ「お、おお」
ワイ「ジャンケンの手やからhands
ね」
娘「そう」
娘「それで」
娘「このhands
っていうオブジェクトから、型を作りたいの」
ワイ「オブジェクトから、型を、作る・・・?」
娘「"グー"
か"チョキ"
か"パー"
っていう文字列しか許可しない・・・」
娘「Hand
型っていうのを定義したいの」
ワイ「なるほどな」
ワイ「それなら・・・」
type Hand = "グー" | "チョキ" | "パー";
ワイ「↑これでええやん」
ワイ「このHand型は"グー"
か"チョキ"
か"パー"
っていう文字列しか許可せえへんで!」
ワイ「union型いうやつやな」
ワイ「これで、間違って不正な値を代入せんようにできるで!」
ワイ「試しに"ピャー"
という文字列を代入しようとすると・・・」
ワイ「ほら、VSCodeくんが赤い波線を表示してくれとる」
ワイ「エラーっちゅうことや」
娘「うん」
娘「でも、それは自分でunion型を書いてるだけでしょ?」
娘「hands
っていうオブジェクトを元に、自動でunion型を作りたいの」
娘「このオブジェクトの持つ値は"グー"
か"チョキ"
か"パー"
って分かり切ってるのに」
娘「わざわざ自分で"グー" | "チョキ" | "パー"
っていうunion型を再定義したくないの」
ワイ「ハハハ」
ワイ「それは、残念ながら無理なご相談や」
ワイ「それに、オブジェクトの中身が書き換えられる可能性もあるやん」
ワイ「いくらconst
で定義しても、オブジェクトは不変ではないんやから」
娘「でも前に、ハスケル子おねえちゃんができるって言ってたよ」
娘「オブジェクトからunion型を生成できるって」
ワイ「(ぐぬぬ)」
ワイ「ええと・・・」
娘「パパ、早く教えて?」
娘「私の稼働がもったいないの」
ワイ「稼働て・・・」
ワイ「(なんか、ワイも前にハスケル子ちゃんから教えてもらった気がするな・・・)」
必死に思い出す
ワイ「せや!」
ワイ「確か、順序としては・・・」
- まずkey名から
"gu" | "choki" | "pa"
というunion型を作る。 - それを使って
"グー" | "チョキ" | "パー"
というunion型を作る。
ワイ「↑こんな感じのはずや」
ワイ「あと・・・」
- keyof演算子
- typeof演算子
ワイ「↑この2つの演算子を使う、って言ってた気がするで」
ワイ「あと、まずは」
ワイ「hands
オブジェクトの最後んところにas const
をつけるんや」
const hands = {
gu: "グー",
choki: "チョキ",
pa: "パー"
} as const; // <- 追加
ワイ「↑こうやな」
ワイ「これでhands
オブジェクトは、中身を書き換えたりできない・・・」
ワイ「readonlyなオブジェクトになったんや」
娘「へぇ〜」
ワイ「このオブジェクトの持つ値は"グー"
か"チョキ"
か"パー"
だって、確定してないとアカンからな」
娘「なるほどね」
ワイ「試しにVSCodeでhands
オブジェクトにマウスを乗せて」
ワイ「hands
オブジェクトの型を確認してみ?」
娘「うん!」
娘「ほんとだ!」
娘「全部のkeyがreadonlyになってる!」
ワイ「これで、hands
オブジェクトのkeyは常にgu
、choki
、pa
であること」
ワイ「valueは常に"グー"
、"チョキ"
、"パー"
であること」
ワイ「それが型によって担保されたで!」
ワイ「ほんまの意味で定数になったってことやな」
娘「だからas const
なんだね!」
typeofで型情報を見る
娘「パパ、それで」
娘「まずkey名から"gu" | "choki" | "pa"
っていうunion型を作るんだっけ?」
娘「どうやってやるの?」
ワイ「まずな、hands
オブジェクトの型情報があったやろ?」
娘「さっきVSCodeで確認したやつね」
ワイ「せや」
ワイ「それに別名を付けてやるんや」
type Hoge = typeof hands;
ワイ「↑こうや」
娘「へぇ〜」
娘「typeof hands
っていうのが」
娘「hands
オブジェクトの型情報、ってことなんだ!」
娘「それにHoge
っていう別名を付けたんだね」
ワイ「せや」
ワイ「またVSCodeでマウスを乗せて確認してみ?」
ワイ「Hoge
型に何が入っとるかを見るんや」
娘「うん!」
娘「ほんとだ」
娘「さっきのhands
オブジェクトの型情報と同じだ」
ワイ「そういう感じや」
keyofで、key名からunion型を定義する
ワイ「次はkeyof
を使って」
ワイ「Hoge
型に存在するkey名からunion型を作るんや」
type Keys = keyof Hoge;
ワイ「↑こうやな」
ワイ「マウスを乗せてKeys
型の中身を見てみ?」
娘「あ、"gu" | "choki" | "pa"
っていうunion型が出来てる」
ワイ「せや」
ワイ「こいつを元に、最終目的である・・・」
ワイ「"グー" | "チョキ" | "パー"
というunion型を作るんや!」
娘「おお〜」
娘「今日のパパ、なんか凄い!」
またtypeofで型情報を見る
ワイ「次にやることは↓こんなイメージや」
type Hand = typeof hands["gu"];
娘「typeof
でhands["gu"]
の型を見て」
娘「その型にHand
っていう別名を付けたんだね」
ワイ「せや」
ワイ「このHand
型の中身は何やと思う?」
娘「ええと」
const hands = {
gu: "グー",
choki: "チョキ",
pa: "パー"
} as const;
娘「まず、hands
オブジェクトの中身は↑こうだから」
娘「hands["gu"]
の中身は"グー"
でしょ?」
娘「だから、要はtypeof "グー"
は何になるか、的な話でしょ?」
娘「じゃあ、型もそのまま"グー"
なんじゃない?」
ワイ「正解や」
娘「念のためマウス乗せて見てみよ」
娘「ほんとだ。Hand
型の中身は"グー"
だ」
ワイ「せやせや」
ワイ「ほな、これはどうや?」
type Hand = typeof hands["gu" | "choki"];
娘「ええと」
娘「hands["gu" | "choki"]
だから」
娘「値としては"グー"
か"チョキ"
が入ってるイメージだよね」
娘「じゃあ、型は"グー" | "チョキ"
かな?」
ワイ「さすが、正解や」
娘「ほんとだ」
娘「ってことは・・・」
type Hand = typeof hands["gu" | "choki" | "pa"];
娘「↑こうすれば・・・」
娘「"グー" | "チョキ" | "パー"
っていうunion型が作れるんだね!?」
娘「私の欲しかったやつ!」
ワイ「せや!」
娘「さっきkeyofで作ったKeys
型が」
娘「"gu" | "choki" | "pa"
っていうunion型だったから」
娘「それを使うんだね!」
ワイ「せや」
娘「つまり・・・」
type Hand = typeof hands[Keys];
娘「↑こうだね!?」
ワイ「さぁ〜、どうやろね」
娘「VSCodeでマウスを乗せて覗いてみる!」
娘「できた!」
娘「hands
オブジェクトからHand
型ができた!」
娘「これで、分かり切った"グー" | "チョキ" | "パー"
を書かなくても」
娘「オブジェクトの値から、自動っぽく型が作れる!」
娘「パパ、ありがとう!」
ワイ「グヘヘ」
娘「試しに不正な値を代入してみよっと!」
娘「あ、ちゃんとエラー扱いになって、波線が表示されてる!」
娘「これで、Hand
型の変数には」
娘「"グー"
か"チョキ"
か"パー"
しか代入できないね!」
娘「不正な値は代入できないね!」
ワイ「せや!」
ワイ「ちなみに、いちいち型エイリアスを作らなくても」
type Hand = typeof hands[keyof typeof hands];
ワイ「↑こう書いても同じことやで」
ワイ「確認のためにVSCodeで見てみると・・・」
ワイ「↑ほらな」
娘「ほんとだ!」
ワイ「せやから、全体のコードとしては・・・」
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で良くない?」
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()
などの反復処理も可能。
娘「↑こんな感じだね!」
ワイ「せや!」
その日の夜
ワイ「はあ、今日は娘ちゃんの役に立てて嬉しいわ」
ワイ「ワイでも技術的なことで役に立てることがあるんやな」
ワイ「滅多にないけどな」
ワイ「・・・せや!」
ワイ「もう一回さっきのコードいじって復習しとこ」
type Hand = typeof hands["guu"];
ワイ「↑こう書くと、確か・・・」
ワイ「Hand
型の中身は"グー"
になるはずやったな」
ワイ「VSCodeで確認してみよ」
ワイ「ファッ!?」
ワイ「アニーになっとるやないかい!」
よめ太郎「エニーや」
よめ太郎「アニーてそれ、ミュージカルやないかい」
よめ太郎「♪トゥモロー、トゥモロー言うてる場合か」
ワイ「そ、それより何で、型が**any
**に・・・」
ワイ「・・・分かった!」
ワイ「VSCodeのバグや!」
よめ太郎「バグってんのはお前の脳みそや」
ワイ「いや、これはワイが正しい気がする」
よめ太郎「タイポしてんねん」
よめ太郎「"gu"
を"guu"
ってミスタイプしてんねん」
ワイ「ファッ!?」
よめ太郎「お前のはTypeScriptやなくてタイポスクリプトや」
ワイ「ぐぬぬ」
よめ太郎「VSCodeはな」
よめ太郎「TypeScriptを作っとるMicrosoftはんが作ってんねや」
よめ太郎「何でお前の方が正しいと思えたねん」
ワイ「テヘヘ・・・」
ワイ「ジョブズに怒られてまうわ」
よめ太郎「Microsoftはビル・ゲイツやろ・・・」
よめ太郎「エグいほど無知やな・・・」
ワイ「ぐぬぬ」
ワイ「いや・・・ワイはジョブズの方が好きやから・・・なんか・・・間違えてもうたわ」
よめ太郎「言い訳が苦し過ぎやろ」
ワイ「い、言い訳ちゃうわ」
ワイ「ほ、ほんまにジョブズが好きなんやワイは!」
よめ太郎「そうか」
よめ太郎「ほな、今から会わせたるわ・・・」
〜おしまい〜