【実務・中級編】 アサーション関数 (assertsキーワード) – TypeScript実践ガイド

フロントエンド開発の現場で、APIからのレスポンスや外部入力の「型安全」に頭を悩ませたことはないだろうか。

「この値は絶対にnullじゃないはずだ」という確信を、わざわざ`if`文で囲んで`if (data !== null)`と書き、その中でさらに型ガードを意識する。あるいは、面倒臭くなって`as`でキャストして、後で痛い目を見る……そんな経験、一度や二度じゃないはずだ。

今日は、そんな泥臭い型定義の悩みから解放してくれる、TypeScriptの強力な隠し玉「アサーション関数(`asserts`キーワード)」について、現場の知見を交えて深掘りしていく。

—

1. なぜ「アサーション関数」が必要なのか?

TypeScriptのコンパイラは、コードの行間を読んで「この変数はこの型のはずだ」と推論してくれる。しかし、実行時の複雑な条件分岐や、外部ライブラリから返ってくる不確かなデータに対しては、コンパイラも無力だ。

通常、ガード句を書けば型は絞り込まれる。だが、もしそのチェックを「別の関数」に切り出したらどうなるか。

function isString(value: unknown) {
return typeof value === ‘string’;
}

const input: unknown = “hello”;

if (isString(input)) {
// ここではinputはstringとして認識される
console.log(input.toUpperCase());
}

これはいい。だが、バリデーションロジックが複雑になり、関数化して「チェックに失敗したらエラーを投げる」という設計にすると、コンパイラは「その関数が何をしたか」を理解できなくなる。そこで登場するのが `asserts` だ。

2. asserts キーワードの正体

`asserts` は、TypeScriptのコンパイラに対して「この関数が正常に終了したなら、引数は間違いなくこの型である」と宣言するものだ。

ブラウザのエンジン(V8など)レベルで見れば、ただの `if` 文と `throw` だが、TypeScriptの型システムにとっては「型情報の強制的な書き換え」という特権的な役割を果たす。

現場で使える「型絞り込み」の極意

例えば、APIから取得したユーザー情報が期待通りの形式か確認するバリデーターを書いてみよう。

/

  • ユーザーオブジェクトであることを保証するアサーション関数

/
function assertIsUser(value: unknown): asserts value is { id: number; name: string } {
// 1. オブジェクトであるか確認
if (typeof value !== ‘object’ || value === null) {
throw new Error(‘ユーザーデータはオブジェクトである必要があります’);
}

// 2. 必要なプロパティが存在するか確認
const user = value as Record;
if (typeof user.id !== ‘number’ || typeof user.name !== ‘string’) {
throw new Error(‘ユーザーデータの形式が不正です’);
}

// ここを抜ければ、コンパイラは引数のvalueを { id: number; name: string } と見なす
}

// — 実際の利用シーン —
const apiResponse: unknown = { id: 1, name: ‘Tanaka’ };

// ここで呼び出すだけで、以降のコードでは型が確定する
assertIsUser(apiResponse);

// 型が確定しているため、補完が効くしエラーも起きない
console.log(apiResponse.id); // 1
console.log(apiResponse.name.toUpperCase()); // TANAKA

3. なぜ「any」や「as」よりも優れているのか

よく現場で見かける「とりあえず `as User` でキャスト」というコードは、「嘘をついている」のと同じだ。実行時に構造が違っていても、型システムはそれを許容してしまうため、予期せぬ実行時エラーを引き起こす。

一方、`asserts` を使うメリットは以下の3点に集約される。

1. 「失敗」を明示的にハンドリングできる: 型チェックと同時にエラーを投げるため、不正なデータがアプリケーションの深層部まで侵入するのを防げる。
2. 型とロジックの同期: 「型定義はこうだけど、バリデーションロジックは別の場所」という、よくある「型と実態の乖離」を最小限に抑えられる。
3. クリーンなコード: `if` 文のネストが減り、ガード句を関数として切り出せるため、ビジネスロジックが読みやすくなる。

4. 実務での使いどころ:ここを狙え

私がプロジェクトで `asserts` を多用するのは、特に「境界線」だ。

  • APIレスポンスのバリデーション: `fetch` の直後、`JSON` をパースした直後に置く。
  • 環境変数のチェック: `process.env.API_KEY` など、存在しないとアプリが起動できない項目に対して使う。
  • フォームバリデーション: `FormData` から取得した値を、特定のDTO(Data Transfer Object)へ変換する際の中間処理。

注意点:使いすぎには注意せよ

何でもかんでも `asserts` にすると、関数呼び出しによるオーバーヘッドや、単なるバリデーション用のコードが肥大化する。基本は `User-Defined Type Guards` (`value is T` を返す関数)を使い、「失敗した時に例外を投げて処理を中断させたい」という明確な意図がある時だけ `asserts` を使うのが、シニアとしての立ち回りだ。

—

最後に:型は「守り」ではなく「攻め」だ

TypeScriptの型定義を、ただエラーを防ぐための窮屈なルールだと思わないでほしい。`asserts` を使いこなすことは、「コードが到達する地点での型を、自分の手でコントロールする」ということだ。

「この値はこうであるはずだ」という確信をコードに託せ。それが堅牢なフロントエンドを構築するための、最も近道になる。

さあ、エディタを開いて、プロジェクトの至る所にある「おまじない的なキャスト」を、この `asserts` で書き換えてみよう。それだけで、君のコードの信頼性は一段階上のレベルへと引き上げられるはずだ。

コメント

タイトルとURLをコピーしました