はじめに
映画が大好きなエンジニア @1129-tame です。
唐突ですが僕は「gifted/ギフテッド」という2017年に公開された映画が大好きです。聡明な少女を主人公とした家族映画で、賢すぎるが故の苦悩といったものが描かれています。
プログラミングの世界にも賢い存在がいます。そうTypeScriptくんです。(賛否両論あると思いますが、あくまで導入ということで)
彼はとても賢いのでうまく利用すれば型を推論してくれます。この頭脳を無駄にしては勿体ない。今回はTypeScriptくんをせっせと型推論させるスマートな方法の一つ「ユーザー定義型ガード」を紹介します。
対象読者
- Typescriptをスマートに書けるようになりたいエンジニア
- JavaScriptの方が好きなエンジニア
型推論とは
まずは基礎から。型推論とは直接型を定義せずともTypeScriptが型を予想してくれることです。
const calc = (a :any, b :any) => {
return a * b
}
例えばこのようなコードは戻り値の型はどうなるでしょうか?
答えは number
型を返してくれます。たしかに数値の計算をしているなら戻り値は数値だろうということは納得できます。これをTypeScriptが勝手にやってくれます。とても賢いですね。(7歳の娘を見ているよう)(そんな娘はいませんが)
let id = 1129; // id is a `number`
let title = "gifted"; // title is a `string`
他にはこのような例があります。let
は書き換え可能なので数字の値はより抽象的なnumber
型になっていますね。
ユーザー定義型ガード
型推論の概要について説明したところで早速ユーザー定義型ガードについて述べていきます。
まずはシンプルなコードから
type Movie = {
id: number
title: string
}
// 受け取ったdataのtitleを返す
function displayTitle(data: Movie) {
console.log(data.title)
}
上記のような単純なコードを題材に説明していきます。これはAPIからMovie情報を取得したdataを引数に置いたメソッドの想定です。dataはMovie型なのでプロパティであるtitle取得時には何の問題もありません。すべてちゃんと型が定義されているのでTypeScriptくんも特に頭を使うことなくスルーです。
type error が発生するコード
ではdataがもしResponceのErrorが返ってくる可能性もあるとしたらどうでしょうか?
type Movie = {
id: number
title: string
}
type ResError = {
message: string
}
function displayTitle(data: Movie | ResError) {
console.log(data.title) // ここでtype errorが発生する
}
プロパティ 'title' は型 'Movie | ResError' に存在しません。 プロパティ 'title' は型 'ResError' に存在しません。
そのまま実装しようとするとtype errorが発生します。dataにはResError型が来る可能性があるので、Typescriptくんに怒られてしまうんですね。ごめんよTypeScriptくん,,,
ユーザー定義型ガードを使おう
こういう時のために、ユーザー定義型ガードを使います。TypeScriptくんに推量させる材料をユーザー側から与えるイメージを持っていると理解しやすいかもしれません。それではコードを見ていきましょう。
type Movie = {
id: number
title: string
}
type ResError = {
message: string
}
// Responce をチェックする関数
function checkResponce(data: Movie | ResError):data is Movie { // ポイント
if (data.constructor.name === 'Error') {
return false
}
return true
}
function displayTitle(data: Movie | ResError) {
if (checkResponce(data)) {
console.log(data.title) // type errorは発生しない(Movie型として認識されている)
} else {
console.log(data.message) // こっちは ResError型になる
}
}
このように関数を追加するとtype errorが発生せずに型を正確に認識を行なってくれます。
ポイントはこちらです。
:data is Movie
このように戻り値を定義すると、戻り値の型はboolean
となり、
true ならばその引数は Movie 型
false ならばその引数は ResError 型
というようにユーザー側が指定することができます。こちらの関数をif文の判定式として利用することで、そのboolean結果によって型推論をTypeScriptくんが行なってくれます。
今回のdisplayTitle
関数の処理をTypeScript目線で追ってみましょう
- おやMovieかResError型らしきdataという値がやってきたぞ?
- このままの状態でプロパティとりだそうとされたら困るなぁ
- お!
checkResponce
関数くんが判定してくれるって。ありがとう - 説明書(型定義)みる限りtrueだったらMovie型って決まってるらしい
- true返ってきた!ならこれはMovie型だからtilte プロパティ取り出していいよー
というような思考の流れをTypeScriptは起こし、処理を行なっていきます。
この文法を知っておくとif分の分岐の際に型が思うように認識されずもがき苦しむことが減ると思います。「ユーザー定義型ガード」は非常に便利だと思うので積極的に使っていきたいですね。
おわりに
型推論のテクニックをTips集としてまとめようと思いましたが、ユーザー方定義ガードで文章量が結構多くなってしまいました。なのでこれから何回かに分けてTypeScriptのテクニックについてまとめていきたいと思います。
冒頭で取り上げた映画もぜひ見てください!
参考文献