【入門編】TypeScriptのプリミティブ型における「小文字」と「大文字」の非対称性:なぜStringを使ってはいけないのか – TypeScript コア・型システムの基礎解析バイブル

こんにちは!TypeScriptの学習、順調に進んでいますか?

他のプログラミング言語(JavaやC#など)を触ったことがある方だと、ついつい「文字の型は`String`、数字の型は`Number`」と、大文字で書きたくなってしまうことってありますよね。TypeScriptでも `let name: String = “TypeScript”;` のように書けてしまうため、そのまま見過ごしてしまいがちです。

でも、ちょっと待ってください。
実はTypeScriptにおいて、小文字の `string` と大文字の `String` は、型システムの中で全く異なる、残酷なほどの非対称性を持っています。ここを誤解していると、コンパイラすら騙されてしまうような「静的型の抜け穴」にハマり、実行時エラーの爆弾を抱えることになってしまいます。

今回は、なぜ `String` を使ってはいけないのか、型システムが内部でどう評価しているのかを、優しく、そして深く紐解いていきましょう。ここをクリアすれば、あなたのTypeScriptの基礎力は一段と強固になりますよ!

—

1. プリミティブ(小文字)とラッパーオブジェクト(大文字)の正体

まずは、JavaScriptおよびTypeScriptにおける「値」と「型」の根本的な違いを確認しておきましょう。

TypeScriptの世界には、大きく分けて以下の2つの住人(概念)がいます。

1. プリミティブ型(小文字: `string`, `number`, `boolean` 等)

  • 値そのものを表す、軽くて純粋なデータです。

2. ラッパーオブジェクト型(大文字: `String`, `Number`, `Boolean` 等)

  • JavaScriptの歴史的背景が生んだ、プリミティブを包み込む「オブジェクト(箱)」です。

図解すると、メモリのイメージはこんな感じです。

[ プリミティブ (string) ] ──> “Hello” (純粋な値そのもの)

[ ラッパーオブジェクト (String) ] ──> [ 箱 ]
└─ 内部に “Hello” を持ち、
さらに何十個ものプロトタイプメソッドがくっついている

JavaScriptでは、私たちが `”hello”.toUpperCase()` のように書いたとき、JavaScriptエンジンが裏側で一時的にこの「ラッパーオブジェクト」に変換してメソッドを呼び出し、終わったら捨ててくれています。だから私たちは普段意識しなくて済んでいるんですね。

—

2. なぜ `String` を使ってはいけないのか?(型安全性の崩壊)

では、本題です。なぜTypeScriptの型注釈に `String`(大文字)を使ってはいけないのでしょうか?

結論から言うと、`String` 型は「文字列だけでなく、`new String(“…”)` で作られた重いオブジェクトも許容してしまうガバガバな型」だからです。

実際のコードでその挙動を見てみましょう。

// ❌ NGな例:大文字の String を使ってしまった場合
const badName: String = “Taro”; // 一見動く

// しかし、こう書くこともできてしまいます!
const terribleName: String = new String(“Taro”);
// ⚠️ terribleName はプリミティブではなく、「Object」です。

console.log(typeof badName); // “string” (プリミティブに見えるが…)
console.log(typeof terribleName);// “object” (なんとオブジェクト!)

「えっ、どっちも文字列を扱えるならいいじゃない」と思いましたか?
ここに、TypeScriptの型安全性を根底から揺るがす大きな罠があります。

比較演算子での「思わぬ落とし穴」

もしあなたのコードで、以下のような厳密等価演算子(`===`)を使ったバリデーションがあったとします。

function validateUser(input: String) {
// ユーザーがプリミティブで渡してきた場合
// input === “Admin” は true になる… かもしれない

if (input === “Admin”) {
console.log(“管理者としてログイン”);
} else {
console.log(“アクセス拒否”);
}
}

// プリミティブを渡す
validateUser(“Admin”); // 「管理者としてログイン」が表示される

// もし、誰かがラッパーオブジェクトを渡してきたら?
validateUser(new String(“Admin”)); // 💥 「アクセス拒否」になってしまう!

なぜ「アクセス拒否」になってしまうのでしょうか?
JavaScriptの `===` は、「値」だけでなく「型(データ構造)」も比較します。プリミティブの `”Admin”` と、オブジェクトである `new String(“Admin”)` は、中身の文字が同じであっても、メモリ上の存在が違うため `false` になってしまうのです。

これはバグの温床になりますよね。

—

3. 正しいアプローチ:常に小文字のプリミティブ型を使う

では、私たちはどう書くべきなのでしょうか?
答えはシンプルです。常に小文字の `string`, `number`, `boolean` を使いましょう。

// ⭕️ GOODな例:小文字の string を使う
const goodName: string = “Taro”; // 完璧!

// 万が一、オブジェクトを代入しようとすると…
// const err: string = new String(“Taro”);
// ❌ コンパイルエラー!
// 型 ‘String’ を型 ‘string’ に割り当てることはできません。
// ‘string’ はプリミティブですが、’String’ はラッパーオブジェクトです。
// 可能であれば ‘string’ を使用してください。

TypeScriptのコンパイラは非常に優秀です。私たちがうっかり `String` オブジェクトを代入しようものなら、親切かつ厳格にエラーを出して止めてくれます。これこそが、TypeScriptの恩恵を受けるということですね。

各プリミティブの対比表

| 意図するデータ | ❌ 避けるべき型 (大文字) | ⭕️ 推奨する型 (小文字) | 理由 |
| :— | :— | :— | :— |
| 文字列 | `String` | `string` | オブジェクトの代入を防ぎ、純粋な文字列のみを許可するため |
| 数値 | `Number` | `number` | `NaN` や `Infinity` を含むプリミティブ数値を安全に扱うため |
| 真偽値 | `Boolean` | `boolean` | `true` / `false` のプリミティブ値に限定するため |

—

まとめ:基本をマスターして揺るぎない型安全性を

今回は、TypeScriptのプリミティブ型における「小文字」と「大文字」の非対称性について解説しました。

  • 大文字の `String` や `Number` は、JavaScriptの歴史的遺物である「ラッパーオブジェクト」を指す。これらを型として使うと、オブジェクトが混入してバグの温床になる。
  • 小文字の `string` や `number` は、純粋なプリミティブ値を表す。型安全性を保つために、常にこちらを優先する。

「たった文字の大文字・小文字の違いでしょ?」と思われがちですが、ここを正しく理解しているかどうかが、保守性の高い堅牢なTypeScriptコードを書けるかどうかの分水嶺になります。

ここをクリアしたあなたなら、もう基本の型で迷うことはありません!
明日からのコーディングで、ぜひ意識してみてくださいね。それでは、快適なTypeScriptライフを!

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