アサーション関数
ここまで、制御フロー分析に伴う型の絞り込みを紹介してきました。次に、制御フロー分析とは別の型の絞り込みを紹介します。それは、アサーション関数です。
例外発生関数の利用
例えば、リスト6の(1)のreturnJSON.result.msgが存在しない場合にエラーを発生させる関数として、リスト12のassertResultMsgIsString()を用意したとします。その上で、extractResult()関数をリスト12のように書き換えたとします。
function assertResultMsgIsString(result: unknown) {
if(result == null || typeof result != "object" || !("msg" in result) || typeof result.msg != "string") { // (1)
throw Error(); // (2)
}
}
function extractResult(returnJSON: ReturnJSON): string {
let result = "通信失敗";
if(returnJSON.status == 1) {
let result = "データ取得失敗";
assertResultMsgIsString(returnJSON.result); // (3)
result = returnJSON.result.msg; // (4)
}
return result;
}
リスト12の(1)はリスト6の(1)のifの内容を反転させた内容となっており、この場合、引数のresultにはmsgは存在しないとして、(2)でエラーを発生させています。そして、extractResult()関数内では、このassertResultMsgIsString()関数を(3)で実行して、msgが存在するかどうかの判定として利用しています。つまり、制御フロー分析の代わりとして利用しています。
しかし、ここで問題となるのは(4)です。(3)でエラーが発生しなかったということは、(4)の段階でreturnJSON.result.msgは存在するはずです。すなわち、ここで型の絞り込みが行われければならないはずです。しかし、TypeScriptのコンパイラはこのことを認識できず、図6のエラーとなってしまいます。
asserts構文
この問題を解決するためにバージョン3.7で導入されたのがassertsキーワードを利用したアサーション関数です。リスト12のassertResultMsgIsString()関数の戻り値の型として、リスト13の(2)の記述をするだけで、図6のエラーは表示されなくなります。この(2)の意味するところは、関数からエラーが発生しなければ、引数であるresultは(1)のResultObj型であることを保証する、という意味です。これにより、型の絞り込みが働くようになります。
interface ResultObj { // (1)
msg: string;
}
function assertResultMsgIsString(result: unknown): asserts result is ResultObj { // (2)
:
}
satisfies演算子
最後に紹介するのは、satisfies演算子です。
ユニオン型の問題
例えば、リスト14の(1)のようなReturnJSON型を考えます。このReturnJSON型は、各プロパティがユニオン型で定義されています。そして、そのReturnJSON型のオブジェクトリテラルとして、(2)のrJsonを定義し、(3)でそのプロパティresultのErrorオブジェクト内のプロパティmessageにアクセスしています。
interface ReturnJSON { // (1)
status: 0 | 1;
result: string | Error;
}
const rJson: ReturnJSON = { // (2)
status: 0,
result: new Error("ダメでした")
};
const error = rJson.result.message; // (3)
このコードは一見、問題ないように思えますが、実は、(3)で図7のエラーとなります。これは、エラーメッセージの通り、resultがstringとErrorのユニオン型であるために起こるエラーです。
satisfiesによる型指定
この問題を解決するためにバージョン4.9で導入されたのがsatisfies演算子による型指定です。リスト14の(2)のrJsonをリスト15のように定義すると、(3)のエラーは解消されます。
const rJson = {
status: 0,
result: new Error("ダメでした")
} satisfies ReturnJSON;
このsatisfiesによる型指定を行うことで、リスト14の(2)の型指定とは違い、rJsonの構成要素が優先され、resultがErrorオブジェクトとして扱われる一方で、例えば、othersのようなReturnJSONに定義されていないプロパティをrJsonに定義しようとすると、エラーとなります。これは、satisfiesによる型指定では、オブジェクトのプロパティ構成がReturnJSONに一致するように緩く判定されるからです。
まとめ
TypeScriptのバージョン5.2までに導入された新機能をテーマごとに紹介する本連載の第4回目はいかがでしたでしょうか。
今回は、型の絞り込みに関するアップデートを紹介しました。次回は、クラス構文に関する新しい仕組みを紹介します。
