01. 背景

TypeScript は何を解決したのか / 型は実行時に消える

📅 作成: 2026-08-28 / 更新: 2026-08-29

この章の到達点

この章の内容

  1. JavaScript の何が問題だったか
  2. TypeScript は「検査して、型を消す」だけ
  3. Java・C# のコンパイルとの違い
  4. 型を付ける他の選択肢

01.1 JavaScript の何が問題だったか

実行するまで壊れていることが分からない

JavaScript には、書いた時点で間違いを教えてくれる仕組みがありません。次のコードはエラーを出さずに動きます

// JavaScript
function greet(user) {
	return `Hello, ${user.name}`;
}

console.log(greet({ namae: "Alice" }));   // "Hello, undefined"

namaename の打ち間違いですが、JavaScript は存在しないプロパティを読んでも undefined を返すだけです。例外も出ず、それらしい文字列が出力されます。この出力を誰かが見つけるまで、間違いは残り続けます。

同じコードに型を付けると、実行する前に止まります。

// TypeScript
type User = { name: string };

function greet(user: User) {
	return `Hello, ${user.name}`;
}

console.log(greet({ namae: "Alice" }));
//                  ~~~~
// error TS2353: Object literal may only specify known properties,
//               and 'namae' does not exist in type 'User'.
//
// 訳: オブジェクト リテラルは既知のプロパティのみ指定できます。
//     'namae' は型 'User' に存在しません。

エラーメッセージは既定で英語です。

この資料では実際に出力される英語をそのまま載せ、日本語訳を添えます。訳は tsc --locale ja の出力に合わせてあります。エラー番号(TS2353 など)で検索する場合、英語のほうが情報が見つかります。

問題は「間違えること」ではなく「気づけないこと」

どの言語でも打ち間違いはします。JavaScript に固有なのは、間違いが実行時まで表面化せず、しかも実行しても例外にならない場合があることです。

ありがちな間違いJavaScript での結果気づけるか
プロパティ名の打ち間違いundefined が返る気づけない
引数の数を間違える足りない引数は undefined気づけない
文字列と数値を取り違える"1" + 1"11" になる気づけない
null のプロパティを読む実行時に例外実行時のみ
関数名の打ち間違い実行時に例外実行時のみ

上の3行はテストを書いても見逃す可能性があります。値が「それらしく」出てしまうためです。TypeScript が解決しようとしたのは、この「気づけなさ」です。

Java・C# から来た人へ

この章の内容は「型がないとこうなる」という当たり前の話に見えるはずです。読み飛ばして 01.2 へ進んで構いません。ただし、TypeScript の型が Java・C# の型とまったく別の仕組みで動いていることは 01.2 と 01.3 で扱います。そこは飛ばさないでください。

01.2 TypeScript は「検査して、型を消す」だけ

tsc がやっていること

TypeScript のコンパイラ tsc がやることは、実質次の2つだけです。

  1. 型注釈をもとにコードを検査する
  2. 型注釈を取り除いた JavaScript を出力する
greet.ts 型注釈あり tsc ① 型を検査する ② 型を取り除く greet.js 型注釈なし 型はここで消える 実行時に型情報は 1 バイトも残らない Node・ブラウザが実行するのはこちら
図 1 — tsc は型を検査し、型を取り除いた JavaScript を出力する

出力を見ると分かる

次の .ts をコンパイルすると、

// greet.ts
type User = { name: string; age: number };

export function greet(user: User): string {
	return `Hello, ${user.name}`;
}

出力される .js はこれだけです。

// greet.js — 型に関する記述がすべて消えている
export function greet(user) {
	return `Hello, ${user.name}`;
}

type User の定義も、引数の : User も、戻り値の : string も残りません。型は開発中にだけ存在する情報です。

この一点が、以降のすべての章に効いてきます。

型が実行時に消えるため、外から入ってくる値(JSON・環境変数・API のレスポンス)が型どおりである保証はどこにもありません。型を書いても、実行時の検証は別途必要です。この扱いは 11. 非同期とエラー処理A3. 実務パターン集 で具体的に扱います。

型エラーがあっても .js は出力される

意外に思われる点ですが、tsc は型エラーを報告しても JavaScript の出力は行います(既定の設定の場合)。型検査と出力は独立した処理だからです。

$ npx tsc greet.ts
greet.ts:7:21 - error TS2353: Object literal may only specify known properties,
                              and 'namae' does not exist in type 'User'.

7 console.log(greet({ namae: "Alice" }));
                      ~~~~

Found 1 error in greet.ts:7

$ ls
greet.js  greet.ts     ← エラーがあっても .js は出力されている

止めたい場合は noEmitOnError を有効にします。設定の詳細は 03. tsconfig で扱います。

動かしてみる

tsx を使うと、コンパイルの手順を意識せずに .ts をそのまま実行できます。

// hello.ts
const name: string = "TypeScript";
console.log(`Hello, ${name}!`);
$ npx tsx hello.ts
Hello, TypeScript!

Node型ストリップ 新しい Node であれば、tsx を使わずそのまま実行することもできます。Node が型注釈を取り除いてから実行するためです。

$ node hello.ts
Hello, TypeScript!

どちらの方法にも制約と使い分けがあります。詳細は 02. 実行環境 で扱います。

01.3 Java・C# のコンパイルとの違い

「コンパイル」という同じ言葉が指すものが違う

Java や C# の経験がある読者ほど、ここで誤解が起きます。どちらもコンパイルと呼びますが、出力されるものも、型情報の行き先も違います。

観点Java / C#TypeScript
出力バイトコード / IL(人が読むものではない)JavaScript(人が読める。元のコードとほぼ同じ形)
型情報実行時まで残る実行時には残らない
型エラー時ビルドが失敗し、成果物ができないエラーを報告するが、出力はされる(既定)
型の判定基準名前で判定する(名前的型付け)形で判定する(構造的型付け → 05
実行するものJVM / CLRJavaScript エンジン(Node・ブラウザ)

Java・C# から来た人へ

Java の instanceof や C# の is にあたる判定を、TypeScript の interface に対しては書けません。interface は実行時に存在しないためです。

interface User { name: string }

// 書けない — User は実行時に存在しない
// if (x instanceof User) { ... }
//                ~~~~
// error TS2693: 'User' only refers to a type, but is being used as a value here.
//
// 訳: 'User' は型のみを参照しますが、ここで値として使用されています。

実行時に「これは User か」を判定するには、自分で判定コードを書きます。その方法は 08. 型の絞り込み で扱います。

リフレクションに相当するものがない

Java の getClass()、C# の typeof(T) のように実行時に型情報を取り出す仕組みは TypeScript にありません。ジェネリクスの型引数も実行時には消えます(09. ジェネリクス)。

この制約は不便に見えますが、理由は単純です。TypeScript は JavaScript に何も足さないためです。出力が普通の JavaScript である以上、JavaScript にない機能を実行時に持ち込むことはできません。

「JavaScript に何も足さない」は設計方針です。

TypeScript は、実行時の挙動を変える機能を原則として追加しません。これにより、既存の JavaScript をそのまま .ts にリネームしても動くという性質が保たれ、段階的な導入が可能になっています。enum のように、この方針の例外にあたる機能もあります(04. 型の基本)。

01.4 型を付ける他の選択肢

JavaScript に型を付ける方法は1つではない

「JavaScript は実行するまで間違いに気づけない」という問題への答えは、TypeScript だけではありません。

TypeScript .ts に型を書く 検査: 強い ビルド: 必要 エディタ支援: 最も厚い 情報量: 最も多い JSDoc .js のコメントに型を書く 検査: 強い(tsc で検査できる) ビルド: 不要 エディタ支援: 厚い 記述: 冗長になりやすい 型を付けない 素の JavaScript 検査: なし ビルド: 不要 エディタ支援: 推測に頼る 小規模なら十分なこともある 共通点 どれも実行するのは JavaScript。型情報は実行時に残らない
図 2 — 型を付ける3つの選択肢。検査の強さとビルドの要否が主な違い

JSDoc という選択肢

.js のままコメントで型を書き、tsc に検査だけさせる方法です。ビルドの手順が増えないのが利点です。

// greet.js — 拡張子は .js のまま
/**
 * @param {{ name: string }} user
 * @returns {string}
 */
function greet(user) {
	return `Hello, ${user.name}`;
}
選ぶ基準向いている方理由
新規のプロジェクトTypeScript記述量が少なく、型の表現力も高い
ビルド手順を増やせないJSDoc.js のまま検査できる
既存の大きな .js 資産があるJSDoc からファイルを変換せず、1ファイルずつ型を足せる
使い捨てのスクリプト型なし型を書く手間が見合わない場合がある

この資料は TypeScript(.ts)を前提に進めます。

JSDoc で書く場合も、型の考え方は同じです。書き方が違うだけなので、以降の章の内容はそのまま使えます。

Flow について

かつて TypeScript と競合した型システムに Flow があります。現在の新規プロジェクトで選ぶ理由はほとんどありませんが、古いコードで // @flow というコメントを見かけたら Flow のコードだと判断できれば十分です。