はじめに
前回の記事では、さまざまな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()
}
}
