TypeScriptの「隠蔽」を極める:Interfaceの private と JavaScriptの #プライベート の境界線
こんにちは。TypeScriptの深淵を歩む皆さん、今日も型定義と格闘していますか?
TypeScriptを使っていると、「このプロパティは外部から触らせたくないな」という場面に出くわしますよね。その際、`private`修飾子を使うべきか、それともJavaScript標準の `#` を使うべきか、迷ったことはありませんか?
実は、この2つは「型レベルの制約」なのか、それとも「実行時の物理的な遮断」なのかという、決定的な違いがあります。今日はその本質を紐解いていきましょう。
—
1. そもそも TypeScriptの `private` は「型」の話でしかない
まず結論から言います。TypeScriptの `private` は、コンパイル後のJavaScriptには一切残りません。
class User {
private secret = “12345”;
}
const user = new User();
// コンパイルすると、ただのプロパティとしてJS上に存在します
console.log((user as any).secret); // “12345” が出力されてしまう!
TypeScriptの `private` は、開発中の「開発者に対する警告」です。IDE上で「そこに触っちゃダメだよ」と教えてくれる親切なガイドであって、実行時のセキュリティを保証するものではないということを、まずは心に刻んでください。
Interfaceで private は定義できる?
ここでよくある疑問が「Interfaceで `private` を指定できるか?」です。
答えは No です。
Interfaceは「公開されている契約(APIの顔)」です。`private` は「隠蔽」を意味するため、そもそも契約書であるInterfaceに `private` を書くという概念自体が矛盾しています。Interfaceはあくまで「外から見て何ができるか」を定義する場所だからです。
—
2. JavaScriptの真のプライベート「#」の登場
そこで登場するのが、ES2022から標準仕様となった `#` プレフィックスです。
class SecureUser {
#secret = “12345”; // ここに注目
getSecret() {
return this.#secret;
}
}
const user = new SecureUser();
// console.log(user.#secret); // コンパイルエラー!
// console.log((user as any).#secret); // 実行時エラー!
こちらは TypeScript の型チェックを通り抜けたとしても、実行時にJavaScriptエンジンがアクセスを物理的にブロックします。 「名前を隠す」のではなく「メモリ上の場所を隠す」というイメージです。
ここで知っておくべき「Interfaceとの相性」
ここが重要なポイントです。`#` プロパティは、Interfaceで定義できません。
interface UserInterface {
// #secret: string; // これは文法エラー!
}
なぜなら、Interfaceは「公開される外部仕様」であり、`#` は「クラス内部の隠し金庫」だからです。`#` はクラスの外からは存在すら確認できないため、Interfaceという「公開インターフェース」の設計図には到底含められないのです。
—
3. どっちを使うべき? 使い分けの黄金律
現場で迷ったときは、この基準で選んでください。
TypeScriptの `private` (または `protected`) を使うべきとき
- チーム開発の安全性を高めたいとき: メンバーが誤って内部メソッドを呼び出さないように、IDEで警告を出したい。
- 継承関係を使いたいとき: `protected` を使えば、サブクラスには許可しつつ、外部には隠すといった柔軟な設計が可能です。
- Interfaceで抽象化したいとき: 「型」としての規約を重視するならこちらです。
JavaScriptの `#` を使うべきとき
- カプセル化を厳密にしたいとき: 外部のJSコードや、悪意のある実行時アクセスから完全にデータを隠蔽したい場合。
- ライブラリ開発など: 利用者に「クラスの内部構造を絶対に触らせない」という強い意思表示をしたいとき。
—
まとめ: TypeScriptを掌握する視点
- TypeScriptの `private`: 開発者のための「交通ルール」。コンパイル後に消える。
- JavaScriptの `#`: 実行時の「物理的な鍵」。Interfaceには書けない。
「Interfaceで隠蔽を定義しようとして悩む」のは、実はあなたが「外部からどう見えるか」を深く考えている証拠です。非常に良い思考の癖ですよ。
TypeScriptの型システムは、コードを「どう読み解くか」を定義し、JavaScriptの実行機能は「どう動くか」を定義します。この2つの層を分けて考えられるようになると、あなたのコードは一段上のレベルに到達します。
もし、クラスの設計で迷ったら、「これは外部との契約(Interface)に含まれるべきか?」と自問自答してみてください。それがクリアできれば、もうTypeScriptの設計で迷うことはありません。
それでは、良いコーディングライフを!