この記事は、TypeScript 4.3で導入されたプライベートクラスフィールドの利用法を共有するためのものです。
プライベートクラスフィールドとはクラス外部から参照不能な変数、関数です。変数や関数の名前を#
で始めると、JavaScriptエンジンはそのフィールドをプライベートだと認識します。この仕様はECMAScriptで提案されています。ステージは3です。
参考 : MDN Web Docs プライベートクラスフィールド
変数のプライベート化はTypeScript 3.8の時点で対応していましたが、4.3では
のプライベート化にも対応しています。
参考記事 : TypeScript 3.8の新機能「ハードプライベート」と従来の「ソフトプライベート」を比べてみた
いままでもTypeScriptにはprivate修飾子がありました。今回導入されたプライベートクラスフィールドと、private修飾子の違いをTypeScript Playgroundで確認します。
テストコードは以下の通りです。
class Foo{
private bar: string = "bar";
#baz:string = "#baz";
readPrivateFields(){
console.log( this.bar );
console.log( this.#baz );
}
}
const foo = new Foo();
foo.readPrivateFields(); // ここは正常に動作する
console.log( foo.bar ); // Error : Property 'bar' is private and only accessible within class 'Foo'.
console.log( foo.#baz ); // Error : Property '#baz' is not accessible outside class 'Foo' because it has a private identifier.
tsconfigのターゲットはESNextとします。
console.log( foo.bar );
// Error : Property 'bar' is private and only accessible within class 'Foo'.
// プロパティ「bar」はプライベートなもので、クラス「Foo」内でのみアクセス可能です。
console.log( foo.#baz );
// Error : Property '#baz' is not accessible outside class 'Foo' because it has a private identifier.
// プロパティ'#baz'は、プライベート識別子を持っているため、クラス'Foo'の外からはアクセスできません。
このコードをそのまま実行すると、コンパイルエラーが発生します。エラーメッセージが変更されているのが分かります。
出力される型定義ファイルは以下のようになります。
declare class Foo {
#private;
private bar;
readPrivateFields(): void;
}
declare const foo: Foo;
注目すべきポイントは#bazの定義です。#bazは型定義ファイルには出力されません。
出力されるJavaScriptファイルのうち、Fooクラス部分は以下のようになります。
"use strict";
class Foo {
constructor() {
this.bar = "bar";
this.#baz = "#baz";
}
#baz;
readPrivateFields() {
console.log(this.bar);
console.log(this.#baz);
}
}
bar
変数のprivate修飾子が消えています。#baz
変数の#修飾は維持されています。
型定義ファイルを捨て、出力されたJavaScriptファイルのみをモジュールとしてimportした場合、bar変数にはアクセスできます。#baz変数にはアクセスできません。
ターゲットをES2015に変更すると、Fooクラスは以下のようになります。
"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Foo_baz;
class Foo {
constructor() {
this.bar = "bar";
_Foo_baz.set(this, "#baz");
}
readPrivateFields() {
console.log(this.bar);
console.log(__classPrivateFieldGet(this, _Foo_baz, "f"));
}
}
_Foo_baz = new WeakMap();
#baz
変数がWeakMapに変換されているのが分かります。
プライベートクラスフィールドが導入されても、private修飾子をすぐに置き換える必要はありません。しかし、将来的なメンテナンス性を考えると、少しずつ置き換えていくのが良さそうです。
以上、ありがとうございました。