2015年、とある休日
よめ太郎「あ・・・あんた!」
よめ太郎「こっち来て!!!」
ワイ「ど、どうしたん?」
ワイ「えらい大っきい声出して」
よめ太郎「娘ちゃんが初めて立ったんや!」
娘(0歳)「ヨチ・・・ヨチ・・・」
ワイ「おお・・・!」
ワイ「これはめでたいこっちゃ・・・!」
娘「・・・パパァ・・・」
ワイ「!?」
ワイ「いま、パパ言うたで!?」
ワイ「立ったと同時に言葉も喋れるんか!?」
ワイ「なんて末恐ろしい子や・・・!」
娘「・・・エテ・・・教エテ・・・」
ワイ「な、なんや喋っとるで・・・」
ワイ「なんや、娘ちゃん・・・?」
娘「・・・パパ・・・」
娘「・・・プログラミング・・・教エテ・・・」
ワイ「ファッ!?」
ワイ「Hello, World!
でええか・・・?」
よめ太郎「いや、Hello, World!
は産声で言うてたわ」
ワイ「せやった!」
ワイ「お医者さんが思わず・・・」
医者「リアルHello Worldしてどないすんねん」
ワイ「ってツッコんでたもんな」
ワイ「ほな、今日は何を教えてほしいんや?娘ちゃん・・・」
娘「・・・ええと・・・」
娘「実は、こないだ6ヶ月検診で行った病院から依頼を受けて」
娘「とある管理画面のフロントエンド部分を開発してるの」
ワイ「もう実務!?」
ワイ「凄いやないか・・・」
よめ太郎「普通に受け入れてるお前が凄いわ」
ワイ「それで、どんな管理画面なんかいな?」
娘「幼児たちの身体測定の結果を記録するための管理画面なの」
ワイ「ふむふむ」
娘「それでね?」
娘「子供たちの身長とか体重、氏名などを記録する必要があるから」
娘「TypeScriptで、こんな型を定義したの」
type Child = {
lastName: string
firstName: string
age: number
height: number
weight: number
}
ワイ「なるほどな」
- 子供は、姓・名・年齢・身長・体重というプロパティを持っている。
ワイ「・・・ということを表しているんやな」
ワイ「ええ感じやないか」
ワイ「それで、いったい何が分からへんのや?」
娘「えっとね」
娘「氏名や年齢を入力して、子供たちの情報を検索する画面があるんだけど」
娘「そこで使う型が上手く書けないの」
ワイ「と言いますと?」
娘「例えば、氏名で検索する場合は」
const condition: Child = {
lastName: "やまだ",
firstName: "たかし"
}
娘「↑こんな感じの条件になるんだけど」
娘「型が合わなくて、エラーになっちゃって」
次のプロパティがありません:
age, height, weight
娘「↑こんなエラーが表示されちゃうの」
ワイ「なるほどな」
ワイ「Child
型のはずなのに、姓と名しか持ってないから・・・」
「年齢と身長と体重が足りまへんがな!」
ワイ「って怒られとるわけやな」
ワイ「そういう場合は、こんな型を書けばええで」
type PartialChild = {
lastName?: string
firstName?: string
age?: number
height?: number
weight?: number
}
ワイ「プロパティ名の後ろに?
を付けてやるんや」
ワイ「姓・名・年齢・身長・体重というプロパティを、全て持っててもいいし」
ワイ「一部だけ持っててもいい、そんな型や」
ワイ「ちなみにPartial
ってのは、部分的って意味や」
const condition: PartialChild = {
lastName: "やまだ",
firstName: "たかし"
}
ワイ「↑ほら、PartialChild
型にしたらエラーが消えたで」
娘「パパ、そんなことは知ってるの」
ワイ「ファッ!?」
娘「そうじゃなくて」
type Child = {
lastName: string
firstName: string
age: number
height: number
weight: number
}
type PartialChild = {
lastName?: string
firstName?: string
age?: number
height?: number
weight?: number
}
娘「↑こんな風に」
娘「ほぼ同じ型を2回も書くのが冗長だから」
娘「もっと効率的な書き方はないのか、ってことを聞きたいの」
ワイ「ぐぬぬ・・・」
娘「Child
型を元にPartialChild
型を作ってくれるような」
娘「そんな、型を作る関数みたいなのはないの?」
ワイ「誠に残念ながら、ございませんねや・・・」
よめ太郎「あるで」
ワイ「ファッ!?」
Partial<Type>
よめ太郎「ほな、コードを書きながら説明していくで」
よめ太郎「Child
型を元にPartialChild
型を作るには・・・」
type PartialChild = Partial<Child>
よめ太郎「↑こうや」
娘「へえ、Partial
ってやつにChild
型を渡すと」
娘「さっきのPartialChild
型と同じものを作って返してくれるんだ!」
よめ太郎「せや」
娘「じゃあ、逆にPartialChild
型からChild
型を作るようなやり方もあるの?」
よめ太郎「あるで」
Required<Type>
type PartialChild = {
lastName?: string
firstName?: string
age?: number
height?: number
weight?: number
}
よめ太郎「↑この型を元に・・・」
type RequiredChild = {
lastName: string
firstName: string
age: number
height: number
weight: number
}
よめ太郎「↑この型を生成したい場合は・・・」
type RequiredChild = Required<PartialChild>
よめ太郎「↑こうや!」
娘「へえ、Required
っていうやつに、PartialChild
を渡すと」
娘「全部のプロパティから?
マークを取ってくれるんだね!」
よめ太郎「そんなイメージや」
Readonly<Type>
娘「じゃあママ」
娘「次は・・・プロパティを上書きできないreadonlyな型を作って!」
よめ太郎「ええで」
よめ太郎「つまり・・・」
type Child = {
lastName: string
firstName: string
age: number
height: number
weight: number
}
よめ太郎「↑このChild
型を元に・・・」
type ReadonlyChild = {
readonly lastName: string;
readonly firstName: string;
readonly age: number;
readonly height: number;
readonly weight: number;
}
よめ太郎「↑こんな型を作ればええんやろ?」
娘「そう!」
よめ太郎「その場合は・・・」
type ReadonlyChild = Readonly<Child>
よめ太郎「↑こうや!」
娘「わぁ、便利だね!」
Omit<Type, Keys>
娘「じゃあママ」
娘「Child
型から、height
プロパティとweight
プロパティだけ削って」
type NameAndAge = {
lastName: string
firstName: string
age: number
}
娘「↑こんな型を作ることもできる?」
よめ太郎「できるで」
type NameAndAge = Omit<Child, "height" | "weight">
よめ太郎「↑こうや!」
娘「わあ、便利!」
ワイ「(いつ便利やねん・・・)」
※筆者注:割と使うときあります。
ReturnType<Type>
娘「じゃあママ」
娘「例えば・・・」
const sameCheck = (a: number, b: number): boolean => a === b
娘「↑こんなsameCheck
っていう関数があるとして」
娘「この関数の、戻り値の型を求めることはできる?」
よめ太郎「できるで」
type SameCheckReturnType = ReturnType<typeof sameCheck>
よめ太郎「↑こうや」
娘「へえ、ReturnType
ってやつにtypeof sameCheck
を渡すと」
娘「sameCheck
関数の戻り値の型を取得できるんだ!」
よめ太郎「せや」
よめ太郎「今回の場合、SameCheckReturnType
型はboolean
型になるっちゅうことやな」
娘「なるほどね〜」
よめ太郎「もしsameCheck
関数の戻り値の型が変わった場合でも」
よめ太郎「SameCheckReturnType
型も追従して変わってくれるから」
よめ太郎「ベタ書きでboolean
って書くより便利やで」
娘「なるほどね!」
娘「ある関数の型を変更した場合に」
娘「連動して他の部分の型も変わってくれるってことだね」
娘「たまに便利そうだね!」
よめ太郎「そういうことや」
Parameters<Type>
よめ太郎「逆に、ある関数の引数の型を取得したい場合は」
type SameCheckParametersType = Parameters<typeof sameCheck>
よめ太郎「↑こうや」
娘「へえ、sameCheck
関数には引数が2つあるけど」
娘「どんな形式で返ってくるの?」
type SameCheckParametersType = [a: number, b: number]
よめ太郎「↑こんな感じや」
娘「へぇ、引数の型はタプル型で返ってくるんだね」
よめ太郎「せや」
Exclude<Type, ExcludedUnion>
娘「じゃあママ」
type Food = "カレー" | "オムライス" | "ラーメン" | "餃子"
娘「↑このFood
型から、中華料理だけを除外したい場合は?」
type FoodExcludeChinese = Exclude<Food, "ラーメン" | "餃子">
/*
FoodExcludeChinese型は
"カレー" | "オムライス"
となる
*/
よめ太郎「↑こうやな」
type Chinese = "ラーメン" | "餃子"
type FoodExcludeChinese = Exclude<Food, Chinese>
よめ太郎「↑こうでもええで!」
娘「なるほど〜」
娘「Excludeって、除外するって意味だもんね!」
Extract<Type, Union>
娘「じゃあママ」
type Hoge = 1 | 2 | 3 | 'a' | 'b' | 'c' | true | false
娘「↑このHoge
型から、true
とfalse
だけ除外したい場合は?」
よめ太郎「ええと、要はboolean
を除外すればええから」
よめ太郎「さっきのExclude
を使って・・・」
type HogeStringNumber = Exclude<Hoge, boolean>
/*
HogeStringNumber型は
1 | 2 | 3 | "a" | "b" | "c"
となる
*/
よめ太郎「↑こうやな」
よめ太郎「もしくは」
よめ太郎「Hoge
型から数値と文字列だけ抽出した型を作る、と考えて」
type HogeStringNumber = Extract<Hoge, string | number>
よめ太郎「↑こうでもええで」
よめ太郎「Extract
は抽出するって意味やからな」
娘「へえ〜」
型関数
娘「すごいね」
娘「引数として型を渡すと、別の型が返ってくる・・・」
娘「型を作る関数みたいだね!」
よめ太郎「まさにそんな感じやな」
よめ太郎「せやから、型関数って呼んどる人たちもおるで」
よめ太郎「正式な用語やないらしいけどな」
娘「へぇ〜、型関数か〜」
よめ太郎「TypeScriptの公式サイトでは、Utility Typesって呼ばれてるみたいや」
よめ太郎「お役立ち型って意味やな」
よめ太郎「その名の通り、けっこう実務で役立つから」
よめ太郎「こんなのがある、ってことだけでも覚えておくとええで!」
娘「わかった!」
娘「ありがとう、ママ!」
よめ太郎「どういたしましてやで!」
娘「あっ!パパ!」
ワイ「なんや?娘ちゃん」
娘「ウンチ出た!」
ワイ「いやそこは赤ちゃんなんかい!」
〜おしまい〜
参考文献
この記事で書いた以外にも色々便利なのがあるから、是非読んでみてくださいやで!