型の絞りこみ~型ガード関数編~【個人学習まとめ】
ユーザー定義の型ガード関数(型述語)
ユーザー定義の型ガード関数って?
開発者が独自に関数を定義して、その関数に渡された値が特定の型であるかどうかをチェックすることができます。
この関数のことをユーザー定義の型ガード関数と呼びます。
型ガード関数
ユーザー定義の型ガード関数を使わない型チェックとユーザー定義の型ガード関数を使った型チェックの両方から違いを確認してみましょう!
ユーザー定義の型ガード関数を使わない場合
まずはユーザー定義の型ガード関数を使わない場合です。
引数がnumber
型かどうか判断する関数を準備します。typeof
演算子を使用します。typeof
演算子の戻り値はBoolean
型でしたね。
function isNumber(value: unknown): boolean {
return typeof value === "number";
}
次に異なるメソッドを呼び出して型チェックを行った結果を出力する関数を準備しますが・・・
function outValue(inputValue: number | string) {
if (isNumber(inputValue)) {
console.log(inputValue.toFixed());
→ プロパティ 'toFixed' は型 'string | number' に存在しません。
プロパティ 'toFixed' は型 'string' に存在しません。
} else {
console.log(inputValue.toLowerCase());
→ プロパティ 'toLowerCase' は型 'string | number' に存在しません。
プロパティ 'toLowerCase' は型 'number' に存在しません。
}
}
toFixed
メソッドとtoLowerCase
メソッドを呼び出す際にエラーとなりました。
if (isString(inputValue)) {
部分だけで考えると、関数isString
の戻り値はBoolean
型なので、引数にnumber
型を渡してあげればtrue
が帰ってくるので、if 文の判定は真となり戻り値にstring
型である情報を取得できるため問題なくtoFixed
メソッドを呼び出すことができそうですよね。
なぜこのような結果になったのでしょうか。
それは TypeScript が分かっているのは関数isString
の戻り値がboolean
型であるということだけだからです。関数isString
で絞り込まれた型の情報は、関数isString
のスコープから外れて瞬間に失われてしまうからです。
変数のスコープについてはこちら
簡単な例でその挙動を確認しましょう。
function function1() {
let x: any;
x = 123.456;
console.log(x.toFixed());
x = "ABCD";
console.log(x.toLowerCase());
return x;
}
上記のコードではany
型の変数x
を返す関数function1
を用意しました。
変数x
を宣言時に初期化せずに宣言したので、値を代入しても変数x
の型はany
型に拡大されています。
しかし、この関数function1
を外部から呼び出して変数value
に代入してみると・・・
関数function1
の戻り値がstring
型と推論されました。
これは関数function1
でreturn
する直前で変数x
に、文字列ABCD
を代入しているため、このような型推論が行われました。
では、最初のコードをもう一度確認しましょう。
//ユーザー定義の型ガード関数を使わない場合
function isNumber(value: unknown): boolean {
return typeof value === "number";
}
function outValue(inputValue: number | string) {
if (isNumber(inputValue)) {
console.log(inputValue.toFixed());
→ toFixedメソッドを呼び出し時にエラー
} else {
console.log(inputValue.toLowerCase());
→ toLowerCaseメソッドを呼び出し時にエラー
}
}
関数isNumber
のスコープ内では、引数value
がnumber
型であることを TypeScript は分かっています。
しかし、関数isNumber
のスコープを外れると、TypeScript は戻り値のboolean
型であることしか分かりません。
よって、関数outValue
内で型の安全性を保つことができないため、エラーが発生します。
このような時にユーザー定義の型ガード関数を使って、関数isString
が型をチェックするための関数であることを TypeScript に教えてあげることができます!
ユーザー定義の型ガード関数を使う場合
ユーザー定義の型ガード関数を使う場合は、対象となる関数の戻り値の型を(引数名) is (型名)
とすることによって指定できます。
(このことを型述語と呼びます。)
実際のコードで確認しましょう。
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function outValue(inputValue: number | string) {
if (isNumber(inputValue)) {
console.log("number型です");
console.log(inputValue.toFixed());
} else {
console.log("string型です");
console.log(inputValue.toLowerCase());
}
}
上記のコードでいうと、関数isNumber
の戻り値部分value is number
が該当します。
この関数が真の値を返した場合に、その引数が指定した型であることを TypeScript の型チェッカーに教えます。
今回でいうと「関数isNumber
がtrue
を返したら、引数value
はnumber
型である」ということになります。
このことから関数outValue
内で関数isNumber
が真として評価された場合は、TypeScript が引数inputValue
はnumber
型である!と理解し、number
型のメソッドであるtoFixed
メソッドを安全に呼び出すことができます。
if 文が偽となれば、引数inputValue
はstring
型であることが分かるので、string
型のメソッドであるtoLowerCase
メソッドを安全に呼び出すことができます。
最後に
最後になってしまいましたが、TypeScript5.5 からは型述語を記述しないような関数でも型ガード関数として機能が発揮されるようになったようです!
// 返り値の注釈がないこの関数は x is number という型述語で推論される
function noAnnotation(x: unknown) {
return typeof x === "number";
}
記事を書いている時に見つけたので「読むのが遅かった~」という気持ちは出てきましたが、こういった歴史があった。ということを頭の片隅にあるとどこかで役に立つかもしれない!ということにして覚えておきたいですね!
最後までありがとうございました。それではノシ
Discussion