はじめに
前回の記事では、さまざまなTypeScriptの型の付け方・扱い方を解説しました。
第3回の今回は「TypeScriptで多様なデータに対応する方法」です。実際にありうるケースを考えながら、TypeScriptで型をあつかっていきます。
第1章 「型ガード」を理解して外部からのデータを扱おう
外部から読み込むデータの問題
TypeScriptで付けた型はコンパイルすると消えます。そのため実行時のコードでは型の確認は行えません。そこで困るのが外部のデータのあつかいです。
ファイル読み込みや通信で取得したデータは、JavaScriptのプログラムで受け取ります。TypeScriptの状態で受け取るわけではありません。そして、そのデータには型情報はなく、事前に静的解析を行えません。
こうした問題に対処する方法が、TypeScriptには用意されています。ここではそうした方法を見ていきます。
制御フロー分析と型ガード
TypeScriptは、変数や関数に付けた型を、代入するタイミングだけで検査しているわけではありません。より高度な処理を行っています。if文やfor文などの処理をたどり、変数に入っている型を絞り込んでいくことができます。
TypeScriptでは、実際にコードが実行された場合の制御フローを分析して、型の可能性を追跡します。こうした機能のことを制御フロー分析と呼びます。この制御フロー分析が、どのように行われるのかを確かめましょう。
まずは問題のあるコードの例を示します。
function getLen(p: string | undefined): number { let len: number = p.length; return len; }
引数p
の文字列長を返す簡単なコードです。しかし、この処理には問題があります。p.length
のp
のところで、'p' is possibly 'undefined'.
(「p」は「未定義」である可能性があります。)というエラーが起きます。
引数p
は、p: string | undefined
と型注釈が付いています。引数p
は、undefined
の可能性があるわけです。
このコードを修正した例を示します。
function getLen(p: string | undefined): number { let len: number = 0; if (typeof p === "string") { len = p.length; } return len; }
if (typeof p === "string")
のところで、変数p
の型を判定しています。ここで型がstringであると絞り込んでいます。そのため、ブロック内のp.length
はエラーになりません。
この変数p
の型の可能性が、どのように絞り込まれているのかを確かめるには、VSCode上で変数にマウスカーソルを重ねて、ポップアップを確認すると分かりやすいです。
typeof p
のところでは、変数p
は(parameter) p: string | undefined
と表示されます。
if文のブロック内のp.length
のところでは、変数p
は(parameter) p: string
と表示されます。型の可能性が絞り込まれているのが分かります。
こうした型のチェックを行うコードのことを、型ガードと呼びます。
さまざまな型ガード方法
型ガードはいくつかの方法で行えます。
まずはtypeof
です。前回登場したtypeof
では、注意すべき点があります。null
の判定です。JavaScriptでは、typeof null
は"object"
を返します。そのため、オブジェクトとnull
の可能性がある時は、さらに踏み込んだ条件分岐が必要になります。
まずは、型ガードがなにもない場合です。
function printKeys1(p: undefined | object | null): void { for (let v of Object.keys(p)) { console.log(v); } } printKeys1({name: "Tom", age: 10});
Object.keys()
の引数はオブジェクトでなければなりません。変数p
は、undefined
の可能性もnull
の可能性もありますのでエラーが起きます。
続いて、オブジェクトであるかを判定したケースです。
function printKeys2(p: undefined | object | null): void { if (typeof p === "object") { for (let v of Object.keys(p)) { console.log(v); } } } printKeys2({name: "Tom", age: 10});
if (typeof p === "object")
で型の可能性を絞り込みました。しかし、変数p
は、null
の可能性が残っているのでエラーが起きます。
次のケースです。
function printKeys3(p: undefined | object | null): void { if (typeof p === "object" && p !== null) { for (let v of Object.keys(p)) { console.log(v); } } } printKeys3({name: "Tom", age: 10});
これでようやくエラーが起きなくなります。&& p !== null
を追加することで、null
の可能性を排除したからです。
次の例です。
型ガードには、特定のクラスのインスタンス化を判定するinstanceof
も使えます。instanceof
を使った型ガードの例を示します。
class Human { walk():void {} } class Bird { fly():void {} } function move(animal: Human | Bird) { if (animal instanceof Human) { animal.walk() } if (animal instanceof Bird) { animal.fly() } }
Human
のインスタンスの場合はwalk
を使い、Bird
のインスタンスの場合はfly
を使っています。
こうした判定は、「プロパティを持つか」を判定するin
演算子でも行えます。例を示します。
function move2(animal: Human | Bird) { if ("walk" in animal) { animal.walk() } if ("fly" in animal) { animal.fly() } }