ウェブエンジニア問題集
第8章

型アサーションと型ガード — as・is・as constの使い分け

18
この章の目次開く

05章で学んだNarrowingは、TypeScriptが制御フローから自動的に型を絞り込む仕組みでした。この章では、開発者が明示的に型を指定する手法を扱います。

学習者学習者

コンパイラが「型が違う」って怒るとき、とりあえず as で黙らせちゃダメなの…?


型アサーション(as)

型アサーションは、TypeScriptの型推論結果を開発者が上書きする手段です。

構文: 値 as 型

// getElementById の戻り値は HTMLElement | null
const input = document.getElementById('my-input') as HTMLInputElement;
 
// HTMLInputElement として扱えるので、input 固有のプロパティが使える
console.log(input.value);
tsx

as を書いた瞬間、コンパイラは推論をやめて「開発者の申告」を信じます。データそのものは何も変わらず、型の見え方だけが変わる点がポイントです。

どういう時に使用するか

具体的な使用シーンは主に以下の3つです。

使用シーン説明
DOM要素を取得する時document.getElementById('my-input') などで要素を取得すると、TypeScriptはそれを単なる「一般的なHTML要素」としか認識しません。HTMLInputElement など具体型に断言することで、.value など要素固有のプロパティがエラーにならずに使えます。
外部データ(APIのレスポンスなど)を受け取る時JSON.parse() の結果など、外部から来たデータは中身がわからないため anyunknown 型になります。「このデータは User 型だ」と断言して、プログラム内で扱いやすくします。
イベントハンドラの引数を使う時クリック時などの event.target は抽象的な型になっているため、ボタンやリンクなど具体的なHTML要素の型に断言して操作します。

コードで書くと次のようになります。

// ① DOM要素を取得する時
const input = document.getElementById('email') as HTMLInputElement;
input.value = 'test@example.com';
 
// ② 外部データ(APIのレスポンスなど)を受け取る時
type User = { id: number; name: string };
const user = JSON.parse(jsonText) as User;
console.log(user.name);
 
// ③ イベントハンドラの引数を使う時
document.querySelector('button')?.addEventListener('click', (event) => {
  const target = event.target as HTMLButtonElement;
  target.disabled = true;
});
tsx

型アサーションは「コンパイラの安全装置をオフにして無理やり通す」行為です。そのため、本当にその型である確証がある時だけ使う(乱用しない)のが鉄則です。

型アノテーションと型アサーションの違い

型アノテーション型アサーション は、

  • 型アノテーション『注釈』

    変数や関数を定義する際に、「これにはこの型のデータが入る」と最初から明示的に指定するもの

  • 型アサーション『断定』

    コンパイラが自動で推論した型に対して、プログラマが「いいや、これはこの型として扱え」と強制的に上書きするものです

どちらも型を指定する役割を持ちますが、使われる目的やタイミングに違いがあります。

// 型アノテーション:定義時に「この型が入る」と宣言する
// 中身が User と合っていなければ、その場でコンパイルエラーになる
const user1: User = { id: 1, name: 'Tanaka' };
 
// 型アサーション:推論結果を「この型として扱え」と上書きする
// 中身が本当に User かどうかはチェックされない
const user2 = JSON.parse(jsonText) as User;
tsx
アノテーションはコンパイラのチェックを受ける「宣言」、アサーションはチェックを迂回する「断言」です。

型アサーションと型キャストの違い

型キャスト は別の型に変換する手段です。

一方、型アサーション はデータそのものは一切変更しません。TypeScriptのコンパイラに対する「見せ方(認識)」だけを変えます。

下記は型キャストの例

const n = 123; // nは数値(Number)
const s = String(n); // sは文字列(String)の "123" に変換される
tsx

非nullアサーション(!)

非nullアサーションは、変数の後ろに ! を付けることで「この値は nullundefined ではない」とコンパイラに断言する記法です。

値が null または undefined になりうる型(例:string | null)に対して、開発者が「ここでは絶対に値が存在する」と保証することで、null チェックなしでプロパティやメソッドにアクセスできるようになります。

function getLength(text: string | null) {
  // ① text は null の可能性があるため、そのまま .length にアクセスするとエラー
  // console.log(text.length); // ❌ 'text' is possibly 'null'.
 
  // ② ! を付けて「null ではない」と断言すると、エラーにならずアクセスできる
  console.log(text!.length);
}
tsx

よく使われるのは、DOM要素の取得結果のように「存在するはずだが、型の上では null の可能性が残る」ケースです。

// getElementById の戻り値は HTMLElement | null
const button = document.getElementById('submit')!;
// ! を付けることで HTMLElement として扱える
button.addEventListener('click', () => {});
tsx

そのため、可能な限り ! ではなく、より安全な代替手段を優先します。

手段特徴
オプショナルチェーン(?.)text?.lengthnull / undefined なら処理を中断し undefined を返す
Null合体演算子(??)text ?? '初期値'null / undefined のときに既定値を使う
型ガード(if文によるチェック)if (text !== null) { ... }実行時に確認するため最も安全

! は「他に手段がなく、かつ値が存在することが確実に分かっている」場合の最後の手段として使うのが鉄則です。

型ガードで型安全を保証する

型ガード(Type Guard)とは、 プログラムの実行時にデータの中身をチェックすることで、TypeScriptの型を安全に絞り込む仕組みです。

function printValue(value: string | number) {
  // ① この時点では string か number か分からないため、文字列専用の value.length はエラーになる
 
  if (typeof value === 'string') {
    // ② 型ガードが働き、このブロック内では確実に「string(文字列)」として認識される!
    console.log(value.length); // エラーにならない
  } else {
    // ③ ここでは自動的に残りの「number(数値)」として認識される
    console.log(value * 10);
  }
}
tsx

typeof のほか、instanceofin を使った基本の絞り込みは05章の型の絞り込み(Narrowing)で扱いました。ここでは、その絞り込みロジックを再利用可能な関数として切り出す方法を見ていきます。

ユーザー定義型ガード(is)

同じ判定ロジックをあちこちの if 文に書くのは冗長です。そこで判定処理を関数に切り出し、戻り値の型の位置に「型述語(type predicate)」を書くことで、呼び出し元でも型の絞り込みが効くようにしたものが、ユーザー定義型ガードです。

構文: function 関数名(引数名: 型): 引数名 is 絞り込み後の型

部分説明
引数名: 型チェックしたい値。外部データの検証では unknown で受けることが多い
引数名 is 絞り込み後の型戻り値の型の位置に書く型述語。この関数が true を返した場合、呼び出し元でその引数が指定した型に絞り込まれる

戻り値: booleantrue = その型である / false = 違う)

典型例は、APIレスポンスのような unknown なデータを検証してから使うパターンです。

type User = {
  id: number;
  name: string;
};
 
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    typeof value.id === 'number' &&
    'name' in value &&
    typeof value.name === 'string'
  );
}
tsx

この isUser を通すと、if ブロックの中では unknownUser として扱えます。

async function fetchUser(): Promise<User | null> {
  const res = await fetch('/api/user');
  const data: unknown = await res.json();
 
  if (isUser(data)) {
    return data; // ✅ このブロック内では data は User 型
  }
  return null; // 検証に失敗したデータは通さない
}
tsx
学習者学習者

結局「これは User だ」って自分で申告してるんだから、as User と同じじゃないの…?

決定的な違いは、実行時チェックの有無です。as は中身を一切確認せずに型だけを上書きしますが、is を使った型ガードは「実行時に中身を検査し、合格したものだけを通す」仕組みです。as は「ノーチェックの自己申告」、is は「実行時検査に合格したことをコンパイラに伝える契約」です。

型ガードのイメージ
型ガードは中身を検査してから通す門番。asは顔パス

型述語は、配列の filter で「絞り込んだ結果の配列」を得たいときにも使えます。

const values = ['a', null, 'b', null]; // (string | null)[]
 
// 型述語をアロー関数の戻り値に書くと、結果が string[] になる
const strings = values.filter((v): v is string => v !== null);
tsx

なお、true / false を返す代わりに「不正なら throw する」形で切り出すアサーション関数(asserts)もあります。こちらは05章のアサーション関数パターンで扱いました。

ダブルアサーション(as unknown as T)の罠

学習者学習者

as any がダメなら as unknown as にすれば安全ですよね? AIに「anyを使わないで」って指示したら、こっちに書き換えてくれたんですけど……

結論から言うと、as unknown as T を書いている時点で型安全性は放棄していますas any as T と本質的な危険度は大差ありません。

// どちらも「型チェックを黙らせる」行為
const a = value as any as TargetType;
const b = value as unknown as TargetType;
tsx

TypeScriptは通常、互換性のない型同士のアサーションをエラーにしてくれます。

const n: number = 42;
const s = n as string; // ❌ エラー:number と string は互換性がない
tsx

ダブルアサーションは、一度 anyunknown を経由することでこの安全装置を迂回します。unknown 経由のほうが @typescript-eslint/no-explicit-any ルールに引っかからないぶん行儀が良く見えますが、やっていることは同じです。

ダブルアサーションが出てきたら、まず設計を見直します。

状況代わりにやること
APIレスポンスの型が合わないジェネリクスで型パラメータを渡す / Zodなどでバリデーション
ライブラリの型定義が古い@types を更新する / 型定義を拡張(declaration merging)
テストでモックを作りたいPartial<T>satisfies を活用する
本当にどうしようもないas unknown as T を使い、コメントで理由を残す
先生先生

as unknown as に統一すること自体は悪くありません。lintルールと整合しますし、コードレビューで「ここは意図的にキャストしている」と分かりやすくなります。ただ、そもそもダブルアサーションが必要な設計自体を見直すほうが先、という順番が大事です。


constアサーション(as const)

as const は名前こそ「アサーション」ですが、これまでの as T とは性格が正反対です。型を偽るのではなく、推論される型をできるだけ狭く・固定的にするための機能で、安全に使えます。

まず、as const なしの推論結果を見てみます。

const config = {
  endpoint: '/api/users',
  method: 'GET',
};
// 推論結果: { endpoint: string; method: string }
tsx

method の値は 'GET' と書いたのに、型は string に**広げられて(widening)**推論されます。オブジェクトのプロパティは後から書き換えられるため、TypeScriptは安全側に倒して広い型を付けるのです。これが困る場面があります。

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
 
function request(url: string, method: HttpMethod) {
  /* ... */
}
 
request(config.endpoint, config.method);
// ❌ エラー:string は HttpMethod に代入できない
tsx

as const を付けると、推論結果がリテラル型のまま固定されます。

const config = {
  endpoint: '/api/users',
  method: 'GET',
} as const;
// { readonly endpoint: "/api/users"; readonly method: "GET" }
 
request(config.endpoint, config.method); // ✅ "GET" は HttpMethod に代入できる
tsx

as const の効果は次の3つです。

効果説明
リテラル型に固定'GET'string に広げられず、"GET" 型のまま保たれる
すべて readonly になるプロパティへの再代入がコンパイルエラーになる(ネストしたオブジェクトも)
配列が読み取り専用タプルになる[10, 20]number[] ではなく readonly [10, 20] になる

配列からユニオン型を導出する定番パターン

as const の実務での代表的な使いどころが、「選択肢の配列」と「その要素のユニオン型」を1つの定義から導出するパターンです。

const CATEGORIES = ['tech', 'design', 'business'] as const;
// 型: readonly ["tech", "design", "business"]
 
type Category = (typeof CATEGORIES)[number];
// 型: "tech" | "design" | "business"
 
// 実行時は配列として使える
CATEGORIES.map((c) => console.log(c));
 
// 型の世界ではユニオン型として使える
function setCategory(category: Category) {
  /* ... */
}
tsx
配列(実行時の値)とユニオン型(型の世界)を1つの定義から導出できるため、選択肢を追加するときの修正が1箇所で済みます。

型アサーション vs 型ガード — どちらを使うべきか

手法安全性使う場面
型ガード(typeof, in, ユーザー定義)高いランタイムで検証できる場合
型アサーション(as T低い型が正しいと確信できる場合
ダブルアサーション(as unknown as T最低テストやモックなどやむを得ない場面のみ
型アサーションと型ガード
迷ったら型ガード。asは「ここは自分が型チェッカーより詳しい」と確信できるときだけ

基本方針:まず型ガードで解決できないか考える。 as は「TypeScriptが推論できないが、自分には型が分かっている」場面の最終手段として使います。

ちゃんと使うためのポイント

  • 型ガードはランタイムで検証するので安全。型アサーションは自己申告なので危険
  • as anyas unknown as T も型安全性を放棄している点は同じ
  • as const はアサーションではなくリテラル型への固定。安全に使える
  • ユーザー定義型ガード(is)はAPIレスポンスのバリデーションで特に有用。ただし検査ロジックの正しさは自分で保証する
  • TypeScript 5.5以降、単純な関数の型述語は自動推論される(特定の型へ絞る場合は明示的に is を書く)

次の章では、PartialPick など、既存の型から新しい型を作り出すユーティリティ型を学びます。この章の表に登場した「モックには Partial<T> を使う」の正体も、そこで分かります。

参考リンク

TypeScriptクイズに挑戦するこの章で学んだTypeScriptの知識を、4択クイズでアウトプットして定着させよう