クラス

クラスはclassで定義できます。またメソッドの定義の仕方やゲッターセッターなどだいたい JavaScript と同じです。

値の初期化

constructorで定義します。

class A {
  value: number;
  
  constructor(value: number) {
    this.value = value;
  }
}

先に使うプロパティの型だけ定義しておいて、constructorでそこに代入します。TypeScript ではこれを、以下のような短縮記法があり、先程の書き方とまったく同じ結果になります。

class A {  
  constructor(public value: number) {
  }
}

publicアクセス修飾子を付けています。これは他にprivateprotectedがあり、それらは以下のそれぞれ意味があります。

  1. publicどこからでもアクセス可能
  2. protectedそれ自身、または継承したものからだけアクセス可能
  3. privateそれ自身からしかアクセスできない

例えば、privateを使う場合はこうなります。

class A {  
  constructor(private value: number) {
  }
}

publicを書き換えただけですね。

constructor 以外で値の初期化

TypeScript のstrictPropertyInitialization設定を有効にしているとconstructorで初期化していないプロパティは警告を受けます。例えば以下のような感じのときです。

class A {
  // Property 'value' has no initializer 
  // and is not definitely assigned in the constructor.
  value: number;

  componentDidMount() {
    this.value = 123;
  }
}

もしcomponentDidMountが100%呼ばれるからvalueにはnumberが入っているんだと自信がある場合は、

class A {
  value!: number;

  componentDidMount() {
    this.value = 123;
  }
}

value!: number;のように!を付けることで警告を回避できます。

スタティック値

スタティック値とは、プロトタイプではないそれ自信のプロパティ(クラス自体に生やす)の値のことです。これを定義するにはpublicのように頭にstaticと書くだけです。

class A {
  static foo = 'static foo';
}

A.foo; // 'static foo'

ジェネリクスなクラス

クラス名のあとにジェネリクス型の設定を書くことができます。

class A<T> {
  // ...
}

constructorまで自分で定義しているなら動的にTを解決できるかもしれませんし、もし設定してからじゃないと使えない場合は、

const a = new A<Foo>();
// T === Foo

のように型を指定して使うことができます。

プライベートフィールド

TypeScript@3.8 (2020-02-20) よりプライベートフィールドが使える様になりました。これはプロパティ宣言名に#プレフィックスを付けることでprivateプロパティのようにそのクラス内でのみ扱うことができるプロパティになります。

class A {
  #foo: string;

  constructor(foo: string) {
    this.#foo = foo;
  }
}

上記のようなコードがあるとして、外部から#fooにアクセスしようとするとエラーになります。

new A("a").#foo;
// Property '#foo' is not accessible outside class 'A' because it has a private identifier.
// プライベート識別子を持つので外部から`#foo`にアクセスできません。

ちなみにこの書き方だと短縮記法は使えません。

class A {
  // error
  constructor(private #foo: string) {
  }
}

JavaScript ではどうなっているか

プライベートフィールド毎にWeakMapを作り、クラスのインスタンスをキーに値を保存しているようです。

// 簡略化してます
let _foo;

class A {
  constructor(foo) {
    _foo.set(this, void 0);
    _foo.set(this, foo);
  }
}

_foo = new WeakMap();

プライベートアクセス修飾子との違い

スーパークラスで定義されたプライベートアクセス修飾子なプロパティはサブクラスで再定義できません。

class SuperA { private value = 123; }

// Class 'A' incorrectly extends base class 'SuperA'. // Types have separate declarations of a private property 'value'. class A extends SuperA { private value = 456; }


プライベートフィールドの場合、クラス内からのみアクセスできるという制約を守りながらスーパー/サブクラス毎に値を設定できます。

```ts
class SuperA {
    #value = 123;

    getFromSuperA() {
        return this.#value;
    }
}

class A extends SuperA {
    #value = 456;
  
   // 上書きしたいならコメントアウト
    // getFromSuperA() {}

    getFromA() {
        return this.#value;
    }
}

console.assert(new SuperA().getFromSuperA() === 123);
console.assert(new A().getFromSuperA() === 123);
console.assert(new A().getFromSuperA() === 456);

またプライベートアクセス修飾子なプロパティはstringによるアクセスを許してますが、プライベートフィールドは許していないという違いもあります。

class A {
  private value = 123;
}

// アクセスできてしまう
console.assert(new A()["value"] === 123);

class B {
  #value = 123;
}

// アクセスできない
// Property '#value' does not exist on type 'A'.
console.assert(new B()["#value"] === 123);