はじめに
こんにちは、H×Hのセンリツ大好きエンジニアです。(同担OKです😉)
2024年6月20日に正式リリースされたTypeScript5.5にて、type predicateの型推論が実装されました。
今回はそれに関してご紹介します!
(情報が誤っている可能性がありますので、その場合は教えていただけると助かります🙇)
参考サイト
- TypeScript 5.5からは関数からType predicatesの型推論が有効になるよ!
- TypeScript 5.5で型述語を推論できて最高。配列のfilterも型安全に
- TS5.5のInferred Type Predicatesでちょこっと気にしておきたいなと思ったこと
type predicateとは
A type predicate is a function whose return type is a boolean value that's used to narrow down types. They come handy when TypeScript can't automatically infer the type of a variable or when more sophisticated type checks are needed.
type predicateは型述語とも呼ばれており、返り値がboolean型の関数で型を絞り込むために利用されます。TypeScriptが変数の型を自動的に推測できない場合であったり、より高度な型チェックが必要な場合に便利です。
。。。自分も言葉だけではあまり理解できませんが、コードを見ていただく方が早いので例を載せます。🥹
// string型
const data = "hello, world!"
// string型か判定する関数
function isString(x: string | undefined): x is string {
return typeof x === "string"
}
// true
console.log(isString(data))
functionの返り値としてx is string
とありますが、これがtype predicateです。
type predicateにより、ユーザ定義関数で型ガードが行えるようになります!
If the function returns true then x has the type T.
If the function returns false then x does not have type T.
今回の場合は、関数がtrue
を返せばx
はstring
型、false
を返せばそれ以外であることを明示的に表すことができるようになり、型を安全に使用することができます。
type predicateがない場合、TypeScriptが型を推論できないため返り値はboolean
になります。
TypeScript5.5以前では出来なかったこと
型ガードを行うことで、より安全にTypeScriptで型を扱えるようになりますが、今まではtype predicateを自分で定義する必要がありました。
そのため、以下のコードのような関数の処理内容と返り値のtype predicateの意味が異なるケースが発生してしまいます。🫣
function isString(x: string | undefined): x is undefined {
return typeof x === 'string'
}
また、string
かundefined
の型を受け取れば良いため、コンパイルする際にもエラーが発生しません。
ユーザが定義する場合、複雑になればなるほどヒューマンエラーのリスクが高まります。
そのため、他の方が見た時に「あれ、これどういう実装?🤔」となってしまいます。
Typescript5.5で実装されたtype predicateの型推論とは
その名の通り、type predicateを自動で推論してくれる機能になります!!
これにより、先程ご紹介したようなヒューマンエラーが起きないようになります☺️
// string型
const data = "hello, world!"
// string型か判定する関数
// ユーザが改めて定義する必要がなく x: string が推論される
function isString(x: string | undefined) {
return typeof x === "string"
}
// true
console.log(isString(data))
この機能により、安全に型ガードを使用できます。
実際の挙動も見てみましょう!
このように、TypeScript5.5では明示的にtype predicateを記述しなくとも、自動で関数の実装内容から推論してくれるようになります!
便利ですね😍
This works because TypeScript now infers a type predicate for the filter function. You can see what’s going on more clearly by pulling it out into a standalone function:
このtype predicateによる型推論が行えるようになったのは、filter
のtype predicateが機能したからのようです。👀
例として、(string | undefined)[]
の配列からstring
型の値のみ取り出すfilter
を作成し、チェックしてみます!
// values: (string | undefined)[]
const values = ["hello", undefined, "world"];
// string型か判定する関数
function isString(x: string | undefined) {
return typeof x === 'string'
}
// valuesからstring型の値のみ格納
const strings = values.filter(isString);
// stringの値を大文字に変更
strings.forEach(str => {
console.log(str.toUpperCase())
});
このコードの出力としては、以下のものが予想できるかと思います。
"HELLO"
"WORLD"
しかし、TypeScript5.5以前ではコンパイルエラーになってしまいます。🥲
実は、TypeScript5.5以前では型推論が行われないため、 filter
を通したとしても型は変更されません。
// strings: (string | undefined)[]
const strings = values.filter(isString);
// 型にundefinedを含んでいるため、コンパイルエラー発生
strings.forEach(str => {
console.log(str.toUpperCase())
});
この問題を、今回の型推論で解決することができます!
filter
を通すことにより型推論が行われ、正しくstring[]
として型定義が行われるようになります🥳
きちんとコンパイルエラーにならず、想定される出力を返してくれますね!
このように、TypeScript5.5で実装されたtype predicateの型推論は開発者が特段型を意識せずに直感的な記述が行えるようになります!
type predicateの型推論が行われる条件
TypeScript will infer that a function returns a type predicate if these conditions hold:
- The function does not have an explicit return type or type predicate annotation.
- The function has a single return statement and no implicit returns.
- The function does not mutate its parameter.
- The function returns a boolean expression that’s tied to a refinement on the parameter.
公式によると、以下の4つの条件を満たした場合だそうです。
- 関数の返り値の型を明示的に指定していない場合
- 関数に単一の
return
があり、かつ暗黙的な返り値がない場合 - 関数の引数が変更されていない場合
- 関数の内容が「引数の型を狭めることに関係するbooleanを返す」ものである場合
1. 関数の返り値の型を明示的に指定していない場合
関数の返り値を以下のように指定されている場合は型推論が発動しなくなります。
function isString(x: string | undefined): x is undefined {
return typeof x === 'string'
}
ですので、上記のようにエラーの元になるため、極力自分で定義しないように心掛けましょう。
2. 関数に単一のreturn
があり、かつ暗黙的な返り値がない場合
暗黙的な返り値についてですが、こちらは真偽チェックが正しく行われているかが影響すると思います。
例えば、isString
関数のreturnを!!x
に変えてみましょう。
すると、コンパイルエラーとなります。
原因としては、!!x
がfalse
を返すものに""
が含まれるからです。(falsy
な値)
これでは、string
型の一部がfalse
になってしまうため、型推論が行えません。
The fix is to tell TypeScript the type that you want using an explicit type annotation:
解決方法としては、明示的な型注釈を利用して必要な型をTypeScriptに伝えてあげることです。
そのため、今回の場合はundefined
を明確にフィルターする必要があります。
function isString(x: string | undefined) {
return typeof x === 'string'
}
3. 関数の引数が変更されていない場合
こちらは、関数内で与えられた引数の型を変更していない場合になります。
例と挙げると、以下のコードが考えられます。
// string型をnumber型に変換し、number型か判定する関数
function isNumber(x: string | undefined) {
const num = Number(x)
return typeof x === 'number'
}
このように、関数の引数が変更されてしまうとTypeScriptで型推論が行われなくなります。
実際にTypeScriptの型定義を見ても分かる通り、x is never
になってしまいます。
なので、上記のように型変更して判定することが出来ませんので、型を変更する関数と型の絞り込みを行う関数は分けてあげましょう。😇
4. 関数の内容が「引数の型を狭めることに関係するbooleanを返す」ものである場合
type predicateの型推論なので、こちらも当然っちゃ当然ですね🫣
こちらは、先ほどから紹介しているfilter
やisString
関数のような、2つ以上の型が引数で与えられた際に、返り値として最低1つは型を絞るような関数の場合に型推論は機能してくれます。
おわりに
今回は先月正式リリースされたばかりの、TypeScript5.5の一機能についてまとめてみました!
参考サイトや公式サイトを見ながら自分なりに解釈しましたが、もし間違いがありましたら教えていただけると幸いです。🙇
最後までご覧いただき、ありがとうございました!