【TypeScript】型ガードを改めて整理する
どうもフロントエンドエンジニアのoreoです。
この記事では代表的な型ガードの方法について整理したいと思います。
型ガードとは、ある値に対して特定の型かどうかチェックし、その結果に応じて処理を分けることを指します。ユーザー定義型ガードや型ガードの変数代入は、知っておくと差がつきますね。
1 typeof演算子
typeof
演算子は、typeof 式
のような形で式を評価し、その評価結果に応じて以下表「結果」のような文字列を返します。式がnull
の場合に、”object"
を返すというイレギュラーな動きをするので、その点注意です。
参考:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/typeof
typeof
演算子を使うと、ユニオン型に対して、型のチェックを行い、処理の分岐が可能です。例えば、下記のように引数の型がstring | number
のユニオン型の場合、number
型であれば、toString()
メソッドで文字列に変換するnumberToStirng
関数のようなものを定義することができます。
function numberToStirng(value: string | number) {
if (typeof value === "number") {
return value.toString();
}
return value;
}
2 instanceof演算子
instanceof
演算子は、値 instanceof クラスオブジェクト
のような形で、値
がクラスオブジェクト
のインスタンスかどうか判定し、真偽値を返します。
instanceof
演算子を使うと、特定のクラスのインスタンスかどうかをチェックして処理の分岐が可能です。以下の例では、date
が、Date
クラスのインスタンスの場合、月を出力します。
function getMonth(date: string | Date) {
if (date instanceof Date) {
console.log(date.getMonth() + 1);
}
}
3 in演算子
in
演算子は、値 in オブジェクト
のような形で、値
が、オブジェクト
のプロパティかどうかを判定し、その真偽値を返します。
in
演算子を使うと、下記のようにpet
がbow
プロパティを持つ場合は、dog
オブジェクトのbow
メソッドを呼び出すことができます。
type Dog = {
bow: () => void;
};
type Cat = {
myao: () => void;
};
const dog: Dog = {
bow: () => console.log("bow")
};
const cat: Cat = {
myao: () => console.log("myao")
};
const pet = (pet: Dog | Cat) => {
if ("bow" in pet) {
dog.bow();
} else {
cat.myao();
}
};
4 ユーザー定義型ガード
4-1 概要
続いてはユーザー定義型ガードです。「1 typeof演算子」で用いたnumberToStirng
関数で、引数value
の型をunknown
にした下記例用いて説明します。
function numberToStirng(value: unknown) {
if (typeof value === "number") {
return value.toString();
}
return value;
}
上記は正常に機能しますが、下記のようにif
文の条件式をisNumber
関数として切り出すと、型のチェックが機能せず、value.toString()
で、value: unknown
、Object is of type 'unknown'
としてエラーとなります。条件式で関数が呼び出された場合、TypeScriptはその関数の定義を見に行き、型の絞り込みをしてくれません。
const isNumber = (value: unknown): boolean => {
return typeof value === "number";
};
function numberToStirng(value: unknown) {
if (isNumber(value)) {
return value.toString(); //エラー!Object is of type 'unknown'
}
return value;
}
型の絞り込みに関数を使いたい場合、ユーザー定義型ガードを使います。
ユーザー定義型ガードは、下記isNumber
関数のように、is
演算子を使用して、返り値の型を引数名 is 型
とします。そして、関数の返り値がtrue
の場合、引数名
に与えられた値が型
に絞り込みます。下記例で言うと、isNumber
関数の返り値がtrue
の場合に、引数名
つまりvalue
が、number
型となります。
const isNumber = (value: unknown): value is number => {
return typeof value === "number";
};
function numberToStirng(value: unknown) {
if (isNumber(value)) {
return value.toString();
}
return value;
}
ただし、ユーザー定義型ガードでバグ(実際の型との乖離)があっても、TypeScriptはそれに気付けず、型安全を破壊する可能性があるので、その点は注意が必要です。
4-2 具体的な利用例
利用例としては、配列から null
やundefined
の型情報を削除する時にユーザー定義型ガードが活躍します。
例えば、下記のような配列array
で、filter
メソッドを用いて、null
とundefined
を排除したとします。値は排除できても、配列filtedArray
の型情報にはnull
とundefined
が残ってしまいます。
const array = ["shinji", null, "asuka", "rei", undefined];
//型:const array: (string | null | undefined)[]
const filtedArray = array.filter((val) => val != null);
console.log(filtedArray); //["shinji", "asuka", "rei"]
//型:const filtedArray: (string | null | undefined)[]
※TypeScriptでは、val != null
で、null
とundefined
の両方をチェックできます。
このような場合、ユーザー定義型ガードを使えば、型情報からもnull
とundefined
を排除することができます。
const filtedArray = array.filter((val): val is string => val != null);
//型:const filtedArray: (string)[]
4-3 (補足)NonNullable<T>
ちなみに、TypeScriptには、ユーティリティ型のNonNullable<T>
があり、これを使うと型T
からnull
とundefined
を排除できます。NonNullable<T>
は、TypeScriptの内部実装的には下記のように実装されています。
type NonNullable<T> = T extends null | undefined ? never : T;
5 型ガードの変数代入
TypeScript4.4以降で使用可能な機能になりますが、型ガードに変数を使うことができます。
例えば、下記のようにdata
がDate
のインスタンスかどうかの型チェックの結果を、変数isDate
に代入することができます。
function getMonth(date: string | Date) {
const isDate = date instanceof Date;
if (isDate) {
console.log(date.getMonth() + 1);
}
}
6 最後に
型ガードの変数代入は初めて知りました。知らない機能がどんどんアップデートされていくので、キャッチアップしていきたいです!
7 参考
Discussion