TypeScriptのクラス入門:constructor・private・readonly・staticを解説
TypeScriptのクラスの定義、コンストラクタ、アクセス修飾子、読み取り専用プロパティ、getter・setterを解説。privateと実行時のプライベート性の違いも扱います。
TypeScriptのクラスは、JavaScriptのクラスを基に、プロパティやメソッドの型を記述できる仕組みです。コンストラクタで初期状態を作り、メソッドを通じてその状態を扱います。
この記事では、初期化、アクセス修飾子、static、readonly、ゲッターとセッターを確認します。各節は独立したファイルです。インストール記事の環境を前提に、TypeScript 4.9.4の strict 設定で確認しています。
クラスの状態とアクセス範囲を定義する
コンストラクタで初期化する
src/class-person.ts を作成します。
export {};
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
introduce(): string {
return `${this.name} / ${this.age}歳`;
}
}
const first = new Person("葵", 30);
const second = new Person("蓮", 25);
console.log(first.introduce()); // 葵 / 30歳
console.log(second.introduce()); // 蓮 / 25歳
new Person(...) ごとに新しいインスタンスが作られます。this.name と this.age は、そのインスタンスのプロパティです。
strict に含まれる strictPropertyInitialization が有効な場合、通常の必須プロパティは宣言時またはコンストラクタで初期化します。後から必ず代入するつもりでも、未初期化のままにすると診断される場合があります。公式のプロパティ初期化の説明
public・private・protectedの役割
public:外部のコードから利用できます。通常、修飾子を省略したメンバーはこの扱いです。private:宣言したクラスの内部から利用します。継承先からの直接参照も制限されます。protected:宣言したクラスと、その継承先から利用します。
src/class-counter.ts は、値の変更をメソッドへまとめた例です。
export {};
class Counter {
private value: number;
constructor(initialValue = 0) {
if (!Number.isSafeInteger(initialValue) || initialValue < 0) {
throw new RangeError("初期値には0以上の安全な整数を指定してください");
}
this.value = initialValue;
}
increment(): void {
if (this.value === Number.MAX_SAFE_INTEGER) {
throw new RangeError("これ以上増やせません");
}
this.value += 1;
}
getValue(): number {
return this.value;
}
}
const counter = new Counter(2);
counter.increment();
console.log(counter.getValue()); // 3
外部のTypeScriptコードから counter.value を参照すると型エラーになります。ただし、private と protected は型チェック上の制約です。生成後のJavaScriptからのアクセスまで遮断する仕組みではありません。公式のアクセス修飾子の注意点
実行時にもプライベートなフィールドを使う
JavaScriptの # 付きフィールドは、実行時にもクラス外からの直接アクセスを制限します。src/class-private-field.ts に保存します。
export {};
class Toggle {
#enabled = false;
switch(): void {
this.#enabled = !this.#enabled;
}
isEnabled(): boolean {
return this.#enabled;
}
}
const toggle = new Toggle();
toggle.switch();
console.log(toggle.isEnabled()); // true
#enabled とTypeScriptの private enabled は、同じ記法の別名ではありません。利用する実行環境と target に応じて、必要な制約を選びます。クライアントへ送信した秘密情報そのものを安全に隠す用途には使えません。
staticとreadonlyを使う
static はクラス自身が持つメンバー、readonly は型チェック上で読み取り専用にするプロパティです。src/class-members.ts の例です。
export {};
class Article {
static createdCount = 0;
constructor(public readonly id: string, public title: string) {
Article.createdCount += 1;
}
}
const first = new Article("article-1", "型の基本");
const second = new Article("article-2", "関数の基本");
first.title = "型の基本を学ぶ";
console.log(first.id); // article-1
console.log(second.title); // 関数の基本
console.log(Article.createdCount); // 2
createdCount には Article.createdCount のようにクラス名でアクセスします。これはこのプログラム内で作ったインスタンス数の例で、サーバーをまたいだ永続的な件数ではありません。
コンストラクタ引数に public や readonly を付ける記法は、プロパティの宣言と初期化をまとめるものです。readonly id は、通常は宣言時またはコンストラクタで設定します。readonly を付けても、オブジェクト全体が実行時に凍結されるわけではありません。
ゲッターとセッターで取得・更新を制御する
ゲッターはプロパティを読むとき、セッターは代入するときに動く処理です。src/class-accessors.ts に保存します。
export {};
class DisplayName {
private storedValue: string;
constructor(initialValue: string) {
this.storedValue = this.normalize(initialValue);
}
private normalize(value: string): string {
const normalized = value.trim();
if (normalized.length === 0) {
throw new Error("表示名は空にできません");
}
return normalized;
}
get value(): string {
return this.storedValue;
}
set value(next: string) {
this.storedValue = this.normalize(next);
}
}
const name = new DisplayName(" 葵 ");
console.log(name.value); // 葵
name.value = " 蓮 ";
console.log(name.value); // 蓮
外部に見せる名前は value、内部に保存する名前は storedValue です。この二つを同じ名前にする必要はありません。ゲッターとセッターを同じプロパティとして使うときは、どちらも value という名前にします。
初期値の検証もコンストラクタで行っているため、作成直後から有効な表示名を保持します。空の文字列を設定しようとすると例外が発生し、元の値は更新されません。
サンプルを実行する
例えば最初の例は次のように実行します。他の節はファイル名を変更してください。
npx tsc src/class-person.ts --strict --target ES2020 --module NodeNext --outDir dist --noEmitOnError
node dist/class-person.js
プロパティは状態、メソッドはその状態を使った処理、アクセス修飾子は型チェック時の利用範囲、と分けて考えるとクラスを読みやすくなります。
クラスに持たせる状態と操作を決める
不正な状態を作らない入口を用意する
コンストラクタと更新メソッドで、値の範囲や状態遷移を確認します。numberという型だけでは、負の在庫や無限大を禁止できません。例外を返した後も状態が妥当なままか、テストしてください。
型のアクセス制限とデータの秘匿を区別する
privateを付けてもクライアントへ送った情報が秘密になるわけではありません。型チェックで操作範囲を制限する設計と、サーバー側で管理する情報の判断は別に行います。