とある休日
娘(5歳)「ねえパパ」
ワイ「なんや?娘ちゃん」
娘「あのね」
娘「TypeScriptを勉強してたら、型ガードっていう概念が出てきたんだけど」
娘「型ガードって何?」
ワイ「ああ、それはな」
ワイ「片面がクッキーで、もう片面がチョコのやつや」
ワイ「ほんで、チョコの面は船の絵みたいになってんねや」
娘「食べてみた〜い」
よめ太郎「それアルフォートやないかい」
ワイ「Oh...よめ太郎...」
よめ太郎「そうやなくて」
よめ太郎「型ガードの話をしてんねん」
よめ太郎「変われ、わしが説明する」
選手交代
よめ太郎「そんで娘ちゃん」
よめ太郎「具体的にどういうタイミングで型ガードが気になったん?」
娘「ええとね」
娘「実は今、遊園地のオンライン申し込みシステムを作っててね」
娘「その遊園地では、12歳以下の子供は料金が半額になるから」
娘「あるユーザーが12歳以下かどうかを判定する関数を書こうとしてたの」
関数名: isChild
- ユーザーが子供料金の対象かどうかを判定する関数
- ユーザーが12歳以下であれば
trueを返す- ユーザーが13歳以上であれば
falseを返す
娘「↑こんな関数が必要なの」
よめ太郎「なるほどな」
娘「それでね」
娘「年齢を登録していないユーザーもいるから」
娘「ユーザーを表す型は↓こんな感じで書いたの」
type User = {
name: string
age: number | null
}
よめ太郎「なるほどな」
よめ太郎「ユーザーの年齢はnullの場合もあるってことやな」
娘「うん」
娘「それで、さっきの子供判定関数を書こうとしてみたの」
const isChild = (user: User): boolean => {
return user.age <= 12
// -> true または false
}
よめ太郎「なるほどな」
よめ太郎「引数としてユーザーを受け取って」
よめ太郎「12歳以下かどうかを真偽値で返すんやな」
娘「うん」
娘「でも、ここでTypeScriptのエラーが出ちゃったの1」
よめ太郎「なるほどな」
よめ太郎「nullかもしれん値を、数値と比較しようとしたからやな」
娘「調べてみたところ」
娘「こういうときには型ガードが必要みたいなんだけど・・・」
ワイ「ちゃうちゃう」
ワイ「こういうときに必要なのは、型アサーションや」
型アサーションしてみる
ワイ「user.ageを数値として扱うために」
ワイ「user.age as numberって書けばええんや」
娘「なるほどね!」
「
user.ageは、ここではnumber型やで!」
娘「↑こんな風に主張するんだね」
娘「アサーションって、主張とか断言って意味だもんね!」
娘「やってみるね!」
娘「あっ!ちゃんとエラーが消えた!」
娘「パパ、ありがとう!」
ワイ「ゲヘヘ」
よめ太郎「いや全然あかんで」
ワイ「なんでや」
よめ太郎「試しに、今の状態のisChild関数を実行してみい」
ワイ「ぐぬぬ、やってみたるわ」
const ojisan: User = {
name: 'やめ太郎',
age: null
}
ワイ「↑まずは、こうやな」
ワイ「試しに1人ユーザーを作ってやるんや」
ワイ「年齢は登録していないパターンにしといたで」
ワイ「ほんで、次は・・・」
console.log(isChild(ojisan))
ワイ「↑こうや!」
ワイ「isChild関数で、ojisanが子供かどうかチェックしたるんや!」
ワイ「結果は・・・!」
true
ワイ「ファッ!?」
ワイ「true!?」
ワイ「ojisanが子供扱いになってもうた・・・」
よめ太郎「そうやで」
よめ太郎「nullと12を比較したら」
よめ太郎「nullの方が小さいことになってしまうねん」
ワイ「マジか」
ワイ「実際には年齢を登録してないだけなのに」
ワイ「12歳以下ってことになってまうやんけ・・・」
よめ太郎「せやからnullと数値の比較とか、せんほうがええで」
よめ太郎「そのために、わざわざコンパイラくんが・・・」
オブジェクトは 'null' である可能性があります。
よめ太郎「↑こんなエラーを出してくれてたんや」
ワイ「なるほどな・・・」
よめ太郎「nullの可能性もあるのに」
よめ太郎「無理やり型アサーションでコンパイラに言うことを聞かせて」
よめ太郎「数値扱いなんてしたらあかんねん」
ワイ「ぐぬぬ・・・」
よめ太郎「そこで、型ガードや」
型ガードしてみる
よめ太郎「こうや!」
const isChild = (user: User): boolean => {
if (user.age === null) return false // <- 追加
return user.age <= 12 // <- 型アサーションはしない
}
ワイ「ほうほう・・・」
ワイ「年齢がnullの場合には、すぐにreturn falseして」
ワイ「子供じゃない扱いにしたるんやね」
よめ太郎「せや」
よめ太郎「user.ageがnull、つまり年齢が未登録の場合は」
よめ太郎「子供料金を適用したらあかんからな」
ワイ「せやな」
よめ太郎「こうすることで、その下の行のuser.ageは」
よめ太郎「nullである可能性が排除されるから・・・」
return user.age <= 12
よめ太郎「↑この部分の型エラーは出なくなるんや」
よめ太郎「数値が来て欲しいところにnullが来てしまう可能性を排除できたんや」
ワイ「ほんまや」
オブジェクトは 'null' である可能性があります。
ワイ「↑このエラーが表示されんくなっとるな」
ワイ「型アサーションも消したのに」
ワイ「ちゃんとuser.ageが数値として認識されとるみたいやな」
娘「そっか」
娘「年齢がnullだった場合には早期リターンされちゃうから」
娘「その先の行までたどり着けないはずだもんね」
娘「この行ではuser.ageは必ずnumber型だ、ってことが保証2されたから」
娘「エラーが出なくなったんだね!」
const isChild = (user: User): boolean => {
// user.age が null なら、次の行で処理は終了する
if (user.age === null) return false
// 従って、ここでは user.age は null になる可能性はなく
// number となる
return user.age <= 12
}
娘「↑こういうことだね!」
よめ太郎「そういうことや」
よめ太郎「TypeScriptのコンパイラくんは」
コンパイラ「
user.ageがnullになる可能性は、関数の1行目で消えたな!」
コンパイラ「ほな、次の行ではuser.ageは確実に数値や!」
よめ太郎「ってことを理解してくれるんや」
ワイ「へえ〜、条件をつけて、先の処理に進める型を絞り込むわけか」
ワイ「でも、何でこれを型ガードって呼ぶんやろ・・・」
なぜに型「ガード」か
よめ太郎「guardって単語は」
よめ太郎「番人、見張り、みたいな意味やから」
型の番人「
numberが来るべきところにnullが入り込まないよう、見張っておきます!」
型の番人「きちんとガードします!」
よめ太郎「↑こんなイメージや」
娘「そう言われてみると、まさに型ガードって感じだね」
ワイ「ほんまや」
ワイ「numberとかnullってのはあくまで今回の例やけど」
ワイ「要するに・・・」
型の番人「この型は、通ってヨシ!」
ワイ「↑こんな感じやな」
まとめ
- 来るべきでないところに
nullやundefinedが来る可能性が残っていると、TypeScriptのコンパイラはエラーを出して教えてくれる - そのとき、型アサーションで無理やりコンパイラに言うことを聞かせるのは、なるべく控えよう
- 型ガードで型を絞り込んで、あるべき型の値だけが先の処理に進めるようにすれば、ちゃんとエラーは消せる
ワイ「ってことやで!娘ちゃん」
娘「うん、聞いてたから知ってる」
ワイ「それな」
〜おしまい〜
おすすめ文献
-
型ガード - TypeScript Deep Dive 日本語版
色んな型ガードの例が紹介されてるで!

