12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

TypeScript 4.3でプライベートクラスフィールド(#)が導入されました

Last updated at Posted at 2021-05-29

はじめに

この記事は、TypeScript 4.3で導入されたプライベートクラスフィールドの利用法を共有するためのものです。

先に結論だけ

  • プライベートクラスフィールドは、変換後のJavaScriptファイルでも維持されます。
  • プライベートメンバーにアクセスしようとすると、ランタイムエラーが発生します。
  • コンパイルエラーだけのprivate修飾子よりも、より厳密にアクセスを制限できます。

プライベートクラスフィールドとは

プライベートクラスフィールドとはクラス外部から参照不能な変数、関数です。変数や関数の名前を#で始めると、JavaScriptエンジンはそのフィールドをプライベートだと認識します。この仕様はECMAScriptで提案されています。ステージは3です。

参考 : MDN Web Docs プライベートクラスフィールド

変数のプライベート化はTypeScript 3.8の時点で対応していましたが、4.3では

  • 関数
  • static変数
  • static関数
  • アクセサーメソッド

のプライベート化にも対応しています。

参考記事 : TypeScript 3.8の新機能「ハードプライベート」と従来の「ソフトプライベート」を比べてみた

TypeScriptのprivate修飾子との違い

いままでも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ファイル

出力される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修飾子をすぐに置き換える必要はありません。しかし、将来的なメンテナンス性を考えると、少しずつ置き換えていくのが良さそうです。

以上、ありがとうございました。

12
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?