Help us understand the problem. What is going on with this article?

5歳娘「パパ、型ガードって何?」

とある休日

娘(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

スクリーンショット 2020-11-19 10.56.49.png

よめ太郎「なるほどな」
よめ太郎「nullかもしれん値を、数値と比較しようとしたからやな」

娘「調べてみたところ」
娘「こういうときには型ガードが必要みたいなんだけど・・・」

ワイ「ちゃうちゃう」
ワイ「こういうときに必要なのは、型アサーションや」

型アサーションしてみる

ワイ「user.age数値として扱うために」
ワイ「user.age as numberって書けばええんや」

娘「なるほどね!」

user.ageは、ここではnumber型やで!」

娘「↑こんな風に主張するんだね」
娘「アサーションって、主張とか断言って意味だもんね!」
娘「やってみるね!」

スクリーンショット 2020-11-19 11.09.09.png

娘「あっ!ちゃんとエラーが消えた!」
娘「パパ、ありがとう!」

ワイ「ゲヘヘ」

よめ太郎「いや全然あかんで

ワイ「なんでや」

よめ太郎「試しに、今の状態のisChild関数を実行してみい」

ワイ「ぐぬぬ、やってみたるわ」

const ojisan: User = {
    name: 'やめ太郎',
    age: null
}

ワイ「↑まずは、こうやな」
ワイ「試しに1人ユーザーを作ってやるんや」
ワイ「年齢は登録していないパターンにしといたで」
ワイ「ほんで、次は・・・」

console.log(isChild(ojisan))

ワイ「↑こうや!」
ワイ「isChild関数で、ojisanが子供かどうかチェックしたるんや!」
ワイ「結果は・・・!」

true

ワイ「ファッ!?
ワイ「true!?」
ワイ「ojisan子供扱いになってもうた・・・」

よめ太郎「そうやで」
よめ太郎「null12を比較したら」
よめ太郎「nullの方が小さいことになってしまうねん」

ワイ「マジか」
ワイ「実際には年齢を登録してないだけなのに」
ワイ「12歳以下ってことになってまうやんけ・・・」

よめ太郎「せやからnullと数値の比較とか、せんほうがええで」
よめ太郎「そのために、わざわざコンパイラくんが・・・」

オブジェクトは 'null' である可能性があります。

よめ太郎「↑こんなエラーを出してくれてたんや」

ワイ「なるほどな・・・」

よめ太郎「nullの可能性もあるのに」
よめ太郎「無理やり型アサーションでコンパイラに言うことを聞かせて」
よめ太郎「数値扱いなんてしたらあかんねん」

ワイ「ぐぬぬ・・・」

よめ太郎「そこで、型ガードや」

型ガードしてみる

よめ太郎「こうや!」

const isChild = (user: User): boolean => {
    if (user.age === null) return false // <- 追加

    return user.age <= 12 // <- 型アサーションはしない
}

ワイ「ほうほう・・・」
ワイ「年齢がnullの場合には、すぐにreturn falseして」
ワイ「子供じゃない扱いにしたるんやね」

よめ太郎「せや」
よめ太郎「user.agenull、つまり年齢が未登録の場合は」
よめ太郎「子供料金を適用したらあかんからな」

ワイ「せやな」

よめ太郎「こうすることで、その下の行の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.agenullになる可能性は、関数の1行目で消えたな!」
コンパイラ「ほな、次の行ではuser.ageは確実に数値や!」

よめ太郎「ってことを理解してくれるんや」

ワイ「へえ〜、条件をつけて、先の処理に進める型を絞り込むわけか」
ワイ「でも、何でこれを型ガードって呼ぶんやろ・・・」

なぜに型「ガード」か

よめ太郎「guardって単語は」
よめ太郎「番人、見張り、みたいな意味やから」

型の番人「numberが来るべきところにnullが入り込まないよう、見張っておきます!」
型の番人「きちんとガードします!」

よめ太郎「↑こんなイメージや」

娘「そう言われてみると、まさに型ガードって感じだね」

ワイ「ほんまや」
ワイ「numberとかnullってのはあくまで今回の例やけど」
ワイ「要するに・・・」

型の番人「この型は、通ってヨシ!」

ワイ「↑こんな感じやな」

まとめ

  • 来るべきでないところにnullundefinedが来る可能性が残っていると、TypeScriptのコンパイラはエラーを出して教えてくれる
  • そのとき、型アサーションで無理やりコンパイラに言うことを聞かせるのは、なるべく控えよう
  • 型ガードで型を絞り込んで、あるべき型の値だけが先の処理に進めるようにすれば、ちゃんとエラーは消せる

ワイ「ってことやで!娘ちゃん」

娘「うん、聞いてたから知ってる」

ワイ「それな」

〜おしまい〜

おすすめ文献

新しい記事もよろしくやで!

Hurry Coderと魔法の数字


  1. TSConfigで、strictまたはstrictNullCheckstrueにしていないとエラーになりません。ぜひtrueにしておきましょう。 

  2. any型を使ってると保証されないから、TSConfigで禁止しましょう。。。 

yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc
http://www.yumemi.co.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away