JSの弱点から学ぶTypeScriptの安全性
はじめに
現在フロントエンド開発のインターンでReact+TypeScriptでの開発を行っています。
「TypeScript」さえ書ければそれでいいと思っていませんか?
JavaScript を学ぶと、その柔軟さの裏にある弱点がわかり、コンパイル元の言語であるTypeScriptの設計がなぜこうなっているのかがわかります。
ここでは、クラス設計を中心に両者の違いを簡潔にまとめます。
要点:JavaScriptでのクラス内の変数は定義の仕方によっては安全でない(バグの温床になる)。TypeScriptはどのような方法を取っているかがわかる。
2025/9/17 追記 TypeScriptにおける#の取り扱いについて理解が誤っていたため、記事の内容を修正しました。コメントいただいたHoney32さんに感謝申し上げます。
以下の本を参考に勉強しています。
りあクト! TypeScriptで始めるつらくないReact開発 第5版【① 言語・環境編】
1. フィールドの定義方法
JavaScript
JavaScriptでは、外からのアクセスを禁止し、クラス内での操作(専用メソッドの作成など)のみを許可するメソッドとして#があります。
class User {
constructor(name) {
this.name = name; // パブリック
}
#secret = "hidden"; // プライベート
}
this.name は外から自由に変更できる
#secret は外から触れない(プライベートフィールド)
TypeScriptはECMAScript の # フィールドをサポートしており、クラス外からアクセスできないことを実行時にも保証します。
TypeScript
TypeScript では、private や protected などのアクセス修飾子によって、コンパイル時にアクセスの安全性を保証しています。ただし、生成された JavaScript では通常のプロパティになるため、実行時には完全な隠蔽はされません。
class User {
constructor(
public name: string, // パブリック
private secret: string // プライベート
) {}
}
TypeScriptのここがすごい!
- アクセス修飾子(
public/private/protected)が使える
アクセス修飾子の種類
private: クラス内からのみアクセス可能。外部やサブクラスからはアクセス不可。
protected: クラス内およびサブクラスからアクセス可能。外部は不可。
public: どこからでもアクセス可能。
#: ECMAScriptの仕様で、クラス内からのみアクセス可能。実行時に完全に隠蔽。
class Animal {
public name: string; // どこからでもOK
protected age: number; // サブクラスまでOK
private secret: string; // クラス内だけ
#hidden: string; // ECMAScriptのプライベートフィールド
constructor(name: string, age: number, secret: string, hidden: string) {
this.name = name;
this.age = age;
this.secret = secret;
this.#hidden = hidden;
}
showSecret() {
return this.secret; // OK(クラス内だから)
}
showHidden() {
return this.#hidden; // OK(クラス内だから)
}
setHidden(value: string) {
this.#hidden = value; // OK(クラス内だから)
}
}
class Dog extends Animal {
bark() {
console.log(this.name); // OK(public)
console.log(this.age); // OK(protected, サブクラスから)
// console.log(this.secret); ❌ private なのでアクセスできない
// console.log(this.#hidden); ❌ #フィールドもアクセス不可
}
}
const d = new Dog("Pochi", 3, "secretValue", "hiddenValue");
console.log(d.name); // OK
console.log(d.showSecret()); // OK
console.log(d.showHidden()); // OK
// console.log(d.age); ❌ protected
// console.log(d.secret); ❌ private
// console.log(d.#hidden); ❌ #プライベートフィールド
2. JavaScriptの弱点とTypeScriptの解決策
① 型がない
const user = { name: "Naoya" };
user.name = 42; // エラーにならない
- TypeScriptでは..
type User = { name: string };
const user: User = { name: "Naoya" };
user.name = 42; // ❌ エラー
② プロパティを勝手に追加できる
class User {
constructor(name) {
this.name = name;
}
}
const u = new User("Naoya");
u.age = 22; // OKになってしまう
- TypeScriptでは..
class User {
constructor(public name: string) {}
}
const u = new User("Naoya");
u.age = 22; // ❌ age は存在しない
③ アクセス修飾子がない
JavaScript: # で表現できるが冗長になることも
TypeScript: コンパイル時に型チェックをするので安全かつアクセス管理の設定可能
④ コンストラクタが冗長
JavaScript:
class User {
constructor(name) {
this.name = name;
}
}
TypeScript(省略記法):
class User {
constructor(public name: string) {}
}
3. 実務での使い分け
JavaScript
- 柔軟だがバグを生みやすい
- 小規模スクリプト向け
TypeScript
- 型と修飾子で安全にクラス設計できる
- 大規模開発やチーム開発に必須
まとめ
JavaScript で見える弱点が、TypeScript の設計意図を理解する助けになることが最近わかってきました。
実務ではTypeScript 前提でクラスを設計するのが基本なので、家でJavaScriptも勉強しつつインターンでTypeScriptも書く、という感じで少しずつ力をつけています。
特に constructor とアクセス修飾子の組み合わせは既存のコードでよく見かけますね。
Discussion
失礼します。
は誤りです。TypeScript のアクセス修飾子(レトロニム:「ソフトプライベート」)のほうが、JavaScript のバードプライベートよりも先発で貧弱です。なぜなら、型チェック時にのみ有効で、無論ランタイムではノーガードだからです。
JavaScript のハードプライベートのほうが後発で強力ですし、もちろん TypeScript もこれを仕様として把握しているので型チェックの対象になります。
よっぽどの「意図」や「プロジェクト特有の事情」がある場合を除いて、ソフトプライベートよりもハードプライベートのほうが優れています。
コメントありがとうございます。TypeScriptのアクセス修飾子の立ち位置について認識が誤っていたようです。内容について修正を加えました。
良いと思います!