TypeScript クラスの型

導入

クラスは、プロパティとメソッドをカプセル化するオブジェクト指向プログラミングの基本コンポーネントであり、TypeScript によって完全にサポートされています。

属性のタイプ

クラス属性は、トップレベルまたはコンストラクター内で宣言できます。

最上位で宣言されたプロパティの場合、宣言時に型を指定できます。

クラスポイント{
  x:数値;
  y:数値;
}

上記の宣言では、属性 xy の型は両方とも number です。

型が指定されていない場合、TypeScript は xy の両方が any 型であると想定します。

クラスポイント{
  ×;
  y;
}

上記の例では、「x」と「y」の型は両方とも「any」です。

宣言時に初期値を与えた場合は型を記述する必要はなく、TypeScript が自ら属性の型を推測します。

クラスポイント{
  x = 0;
  y = 0;
}

上の例では、属性 xy の型は数値であると推測されます。

TypeScript には設定項目「strictPropertyInitialization」があり、これがオンになっている限り (デフォルトでオンになっています)、プロパティに初期値が設定されているかどうかがチェックされ、初期値が設定されていない場合はエラーが報告されます。

// strictPropertyInitialization をオンにする
クラスポイント{
  x: 数値; // エラーレポート
  y: 数値; // エラーレポート
}

上記の例では、クラスの最上位属性に値が割り当てられていない場合、エラーが報告されます。エラーが発生したくない場合は、null 以外のアサーションを使用できます。

クラスポイント{
  x!: 数値;
  y!: 数値;
}

上記の例では、属性 xy には初期値がありませんが、属性名の後に感嘆符が追加されており、これら 2 つの属性が空ではないことを示しているため、TypeScript はエラーを報告しません。詳細については、「型アサーション」の章を参照してください。

読み取り専用修飾子

プロパティ名の前に readonly 修飾子を追加すると、プロパティが読み取り専用であることを示します。インスタンス オブジェクトはこのプロパティを変更できません。

クラスA {
  読み取り専用 ID = 'foo';
}

const a = 新しい A();
a.id = 'bar' // エラーレポート

上の例では、「id」属性の前に readonly 修飾子が付いています。インスタンス オブジェクトがこの属性を変更すると、エラーが報告されます。

readonly 属性の初期値は、トップレベルの属性またはコンストラクターに書き込むことができます。

クラスA {
  読み取り専用 ID:文字列;

  コンストラクター() {
    this.id = 'bar' // 正しいです。
  }
}

上記の例では、読み取り専用プロパティの初期値がコンストラクター内で設定されていますが、これは問題ありません。

クラスA {
  読み取り専用 id:string = 'foo';

  コンストラクター() {
    this.id = 'bar' // 正しいです。
  }
}

上記の例では、コンストラクターが読み取り専用プロパティの値を変更することも可能です。つまり、読み取り専用属性の値が 2 か所に設定されている場合は、コンストラクター メソッドが優先されます。他のメソッドで読み取り専用プロパティを変更すると、エラーが発生します。

メソッドの種類

クラスメソッドは通常の関数であり、型宣言方法も関数と同様です。

クラスポイント{
  x:数値;
  y:数値;

  コンストラクター(x:数値, y:数値) {
    this.x = x;
    this.y = y;
  }

  add(ポイント:ポイント) {
    新しいポイントを返します(
      this.x + point.x、
      this.y + point.y
    );
  }
}

上記の例では、コンストラクタメソッド constructor() と通常のメソッド add() は両方ともパラメータの型を示していますが、戻り値の型は TypeScript が独自に推論できるため省略されています。

クラスメソッドは通常の関数と似ており、パラメータのデフォルト値や関数のオーバーロードを使用できます。

以下はパラメータのデフォルト値の例です。

クラスポイント{
  x: 数値。
  y: 数値。

  コンストラクター(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
}

上記の例では、新しいインスタンスの作成時に属性 xy の値が指定されなかった場合、それらはデフォルト値 0 と等しくなります。

以下は関数のオーバーロードの例です。

クラスポイント{
  コンストラクター(x:数値, y:文字列);
  コンストラクター(s:文字列);
  コンストラクター(xs:数値|文字列, y?:文字列) {
    // ...
  }
}

上記の例では、コンストラクターは 1 つのパラメーターまたは 2 つのパラメーターを受け入れることができ、型宣言に関数のオーバーロードを使用します。

さらに、コンストラクターは常にインスタンス オブジェクトを返すため、コンストラクターは戻り値の型を宣言できません。宣言しないとエラーが報告されます。

クラス B {
  constructor():object { // エラーレポート
    // ...
  }
}

上記の例では、コンストラクターが戻り値の型 object を宣言しているため、エラーが発生します。

アクセサメソッド

アクセサは、ゲッターやセッターなどの特別なクラス メソッドです。

これらは特定の属性の読み取りと書き込みに使用され、ゲッターは属性の読み取りに使用され、ストアラーは属性の書き込みに使用されます。

クラスC {
  _name = '';
  名前を取得() {
    これを返します。_name;
  }
  名前(値)を設定 {
    this._name = 値;
  }
}

上の例では、get name() が valuer で、get がキーワード、name が属性名です。 name 属性が外部から読み取られると、インスタンス オブジェクトは自動的にこのメソッドを呼び出し、このメソッドの戻り値は name 属性の値になります。

set name() は値を節約するもので、set はキーワード、name は属性名です。 name 属性が外部で書き込まれると、インスタンス オブジェクトは自動的にこのメソッドを呼び出し、割り当てられた値を関数パラメータとして渡します。

TypeScript には、アクセサーに関する次のルールがあります。

(1) プロパティに「get」メソッドのみがあり、「set」メソッドがない場合、そのプロパティは自動的に読み取り専用プロパティになります。

クラスC {
  _name = 'foo';

  名前を取得() {
    これを返します。_name;
  }
}

const c = 新しい C();
c.name = 'bar' // エラーレポート

上の例では、name 属性に set メソッドがないため、この属性に値を割り当てるときにエラーが報告されます。

(2) TypeScript バージョン 5.1 より前では、set メソッドのパラメーターの型は、get メソッドの戻り値の型と互換性がある必要があります。そうでない場合は、エラーが報告されます。

// TypeScript バージョン 5.1 より前
クラスC {
  _name = '';
  get name():string { // エラーレポート
    これを返します。_name;
  }
  セット名(値:数値) {
    this._name = 文字列(値);
  }
}

上記の例では、get メソッドの戻り値の型が文字列であり、set メソッドのパラメータの型である number と互換性がないため、エラーが発生します。これを次のように変更すると、エラーは報告されなくなります。

クラスC {
  _name = '';
  名前を取得():文字列 {
    これを返します。_name;
  }
  名前を設定(値:数値|文字列) {
    this._name = 文字列(値);
  }
}

上記の例では、set メソッドのパラメーターの型 (number|string) は、get メソッドの戻り値の型 (string) と互換性があり、許可されています。

TypeScript バージョン 5.1 では 変更 が行われ、現在はこの 2 つになっています。互換性がない可能性があります。

(3) 「get」メソッドと「set」メソッドのアクセス可能性は、パブリック メソッドとしてもプライベート メソッドとしても一貫していなければなりません。

プロパティインデックス

クラスを使用すると、属性インデックスを定義できます。

クラス MyClass {
  [s:string]: ブール値 |
    ((s:string) => ブール値);

  get(s:string) {
    this[s] をブール値として返します。
  }
}

上記の例では、[s:string] は属性名のタイプが string であるすべての属性を表し、その属性値はブール値またはブール値を返す関数のいずれかです。

クラス メソッドは特別な種類のプロパティ (値が関数であるプロパティ) であるため、プロパティ インデックスの型定義にもメソッドが含まれることに注意してください。オブジェクトがプロパティ インデックスとメソッドの両方を定義する場合、前者には後者の型が含まれている必要があります。

クラス MyClass {
  [s:string]: ブール値;
  f() { // エラーを報告する
    true を返します。
  }
}

上記の例では、属性インデックスのタイプにメソッドが含まれていないため、後続のメソッド f() の定義でエラーが直接報告されます。正しい書き方は以下の通りです。

クラス MyClass {
  [s:string]: ブール値 (() => ブール値);
  f() {
    true を返します。
  }
}

プロパティ アクセサーはプロパティと同様に扱われます。

クラス MyClass {
  [s:string]: ブール値;

  get isInstance() {
    true を返します。
  }
}

上記の例では、属性 inInstance のリーダーは関数メソッドですが、属性とみなされます。そのため、属性インデックスにはメソッドの種類が含まれませんが、エラーは報告されません。

クラスのインターフェースインターフェース

キーワードを実装します

インターフェイス インターフェイスまたは型エイリアスをオブジェクトの形式で使用して、クラスの一連のチェック条件を指定できます。次に、クラスは、implements キーワードを使用して、現在のクラスがこれらの外部型条件の制約を満たしていることを示します。

インターフェース 国 {
  名前:文字列;
  大文字:文字列;
}
// または
タイプ 国 = {
  名前:文字列;
  大文字: 文字列;
}

クラス My Country は Country {を実装します。
  名前 = '';
  大文字 = '';
}

上の例では、interface または type のいずれかでオブジェクト タイプを定義できます。クラス My Country は、キーワード implements を使用して、このクラスのインスタンス オブジェクトがこの外部型を満たすことを示します。

インターフェイスはチェック条件を指定するだけです。これらの条件が満たされない場合、エラーが報告されます。クラス自身の型宣言を置き換えるものではありません。

インターフェース A {
  get(名前:文字列): ブール値;
}

クラス B は A を実装します {
  get(s) { // s の型は任意です
    true を返します。
  }
}

上記の例では、クラス B はインターフェイス A を実装していますが、後者は B の型宣言を置き換えません。したがって、Bget() メソッドのパラメータ s の型は string ではなく any になります。クラス B はパラメータ s の型を宣言する必要があります。

クラス B は A を実装します {
  get(s:string) {
    true を返します。
  }
}

別の例を示します。

インターフェース A {
  x: 数値。
  y?: 数値;
}

クラス B は A を実装します {
  x = 0;
}

const b = 新しい B();
b.y = 10; // エラーレポート

上記の例では、インターフェース A にはオプションの属性 y があり、クラス B はこの属性を宣言していないため、型チェックに合格できます。ただし、「B」のインスタンス オブジェクトの属性「y」に値を代入すると、エラーが報告されます。したがって、クラス B はオプションの属性 y を宣言する必要があります。

クラス B は A を実装します {
  x = 0;
  y?: 数値;
}

同様に、クラスは、インターフェイスによって宣言されていないメソッドとプロパティを定義できます。

インターフェースポイント{
  x: 数値。
  y: 数値。
}

クラス MyPoint は Point {を実装します
  x = 1;
  y = 1;
  z:数値 = 1;
}

上記の例では、「MyPoint」クラスは「Point」インターフェースを実装していますが、追加の属性「z」も内部的に定義しています。これは、インターフェースによって指定された条件を満たすことに加えて、クラスが追加の条件を持つことを示しています。 。

implements キーワードの後に​​は、インターフェイスだけでなく、別のクラスも指定できます。このとき、以下のクラスがインターフェースとして扱われます。

クラスカー{
  id:番号 = 1;
  move():void {};
}

クラス MyCar は Car {を実装します。
  id = 2 // 省略不可
  move():void {} // 省略不可
}

上記の例では、implements の後にクラス Car が続きます。この時点で、TypeScript は Car をインターフェイスとみなし、MyCarCar 内のすべてのプロパティとメソッドを実装する必要があります。そうしないと、エラーが発生します。報告した。したがって、現時点では、Car クラスは一度実装されているため、MyCar クラス内の属性やメソッドを省略することはできません。

インターフェイスはクラスの外部インターフェイスを記述します。つまり、インスタンスのパブリック プロパティとパブリック メソッドは定義できないことに注意してください。これは、TypeScript の設計者は、プライベート プロパティはクラスの内部実装であり、インターフェイスはテンプレートとして機能し、クラスの内部コードの作成に関与すべきではないと考えているためです。

インターフェース Foo {
  プライベートメンバー:{} // エラーレポート
}

上記の例では、インターフェース Foo にプライベート プロパティがあり、エラーが報告されます。

複数のインターフェースを実装する

クラスは複数のインターフェイスを実装でき (実際には、複数の制限を受け入れます)、各インターフェイスはカンマで区切られます。

クラス Car は、MotorVehicle、Flyable、Swimmable {を実装します。
  // ...
}

上記の例では、Car クラスは、MotorVehicleFlyable、および Swimmable の 3 つのインターフェイスを同時に実装します。これは、これら 3 つのインターフェイスによって宣言されたすべてのプロパティとメソッドをデプロイし、すべての条件を満たす必要があることを意味します。

ただし、複数のインターフェイスを同時に実装することは適切な記述方法ではなく、コードの管理が困難になりやすいため、代わりに 2 つの方法を使用できます。

1 つ目の方法はクラスの継承です。

クラス Car は MotorVehicle {を実装します
}

class SecretCar extends Car 実装 Flyable、Swimmable {
}

上記の例では、Car クラスは MotorVehicle を実装し、SecretCar クラスは Car クラスを継承して、SecretCar と同等の 2 つのインターフェイス FlyableSwimmable を実装します。 3 つのインターフェイスを同時に実装するクラス。

2 番目の方法はインターフェイスの継承です。

インターフェース A {
  a:数字。
}

インターフェイス B は A を拡張します {
  b:数字;
}

上記の例では、インターフェイス 'B' はインターフェイス 'A' を継承します。クラスがインターフェイス 'B' を実装している限り、インターフェイス 'A' と 'B' の両方を実装することと同じです。

前の例は、インターフェイスの継承を使用して書き直すことができます。

インターフェース MotorVehicle {
  // ...
}
インターフェイス フライアブル {
  // ...
}
インターフェイス スイマブル {
  // ...
}

SuperCar インターフェイスは MotorVehicle、Flyable、Swimmable を拡張します {
  // ...
}

クラス SecretCar は SuperCar を実装します {
  // ...
}

上記の例では、クラス SecretCar は、SuperCar インターフェイスを通じて複数のインターフェイスを間接的に実装します。

複数の実装が発生する場合 (つまり、1 つのインターフェイスが同時に複数のインターフェイスを実装する場合)、異なるインターフェイスが競合する属性を持つことができないことに注意してください。

インターフェイス フライアブル {
  foo:数値;
}

インターフェイス スイマブル {
  foo:文字列;
}

上記の例では、属性 foo が 2 つのインターフェースで異なる型を持っています。これらの 2 つのインターフェースを同時に実装すると、エラーが報告されます。

クラスとインターフェースのマージ

TypeScript では、同じ名前を持つ 2 つのクラスは許可されませんが、クラスがインターフェイスと同じ名前を持つ場合、インターフェイスはクラスにマージされます。

クラスA {
  x:数値 = 1;
}

インターフェース A {
  y:数値;
}

a = 新しい A(); とします。
a.y = 10;

a.x // 1
a.y // 10

上記の例では、クラス A とインターフェイス A は同じ名前を持ち、後者は前者の型定義にマージされます。

クラスにマージされた非 null プロパティ (上記の例では「y」) は、代入前に読み取られた場合は「未定義」を返すことに注意してください。

クラスA {
  x:数値 = 1;
}

インターフェース A {
  y:数値;
}

a = 新しい A(); とします。
a.y // 未定義

上記の例では、型定義に従って、「y」は null 以外のプロパティである必要があります。ただし、マージ後は、「y」が「未定義」になる可能性があります。

クラスタイプ

インスタンスタイプ

TypeScript クラスはそれ自体が型ですが、クラス自体ではなく、クラスのインスタンス型を表します。

クラス カラー {
  名前:文字列;

  コンストラクター(名前:文字列) {
    this.name = 名前;
  }
}

const green:Color = new Color('green');

上記の例では、クラス「Color」が定義されています。そのクラス名は型を表しており、インスタンスオブジェクト green はこの型に属します。

インスタンス オブジェクトを参照する変数の場合、どちらもインスタンス オブジェクトの型を表すため、型をクラスまたはインターフェイスとして宣言できます。

インターフェース MotorVehicle {
}

クラス Car は MotorVehicle {を実装します
}

//書き方その1
const c1:Car = 新しい Car();
//書き方2
const c2:MotorVehicle = 新しい Car();

上記の例では、変数タイプはクラス Car またはインターフェイス MotorVehicle として記述できます。それらの違いは、クラス Car がインターフェイス MotorVehicle にないプロパティとメソッドを持っている場合、変数 c1 だけがこれらのプロパティとメソッドを呼び出せることです。

型として使用される場合、クラス名はインスタンスの型のみを表すことができ、クラス自体の型は表すことができません。

クラスポイント{
  x:数値;
  y:数値;

  コンストラクター(x:数値, y:数値) {
    this.x = x;
    this.y = y;
  }
}

// 間違い
関数createPoint(
  ポイントクラス:ポイント、
  x: 数値、
  y:数値
) {
  新しい PointClass(x, y) を返します。
}

上記の例では、関数 createPoint() の最初のパラメータ PointClass を Point クラスに渡す必要がありますが、パラメータの型が Point として記述されている場合、Point であるためエラーが報告されます。クラス自体の型ではなく、インスタンスの型を説明します。

クラス名は型として使用され、実際にオブジェクトを表すため、クラスはオブジェクト型として名前を付けることができます。実際、TypeScript には、オブジェクト タイプに名前を付ける方法が 3 つあります。タイプ、インターフェイス、クラスです。

クラス独自の型

クラス独自の型を取得するには、typeof 演算子を使用するのが便利です。

関数createPoint(
  PointClass: ポイントのタイプ、
  x:数値、
  y:数値
):ポイント {
  新しい PointClass(x, y) を返します。
}

上記の例では、createPoint() の最初のパラメータ PointClassPoint クラスそのものです。このパラメータの型を宣言するには、typeof Point を使用するのが簡単な方法です。 Point クラスは値であるため、typeof Point はこの値の型を返します。 createPoint() の戻り値の型はインスタンスの型を表す Point であることに注意してください。

JavaScript 言語では、クラスはコンストラクターの糖衣構文の一種にすぎず、本質的にはコンストラクターを記述する別の方法です。したがって、クラス独自の型をコンストラクターの形式で記述することができます。

関数createPoint(
  PointClass: new (x:number, y:number) => ポイント、
  x: 数値、
  y: 数値
):ポイント {
  新しい PointClass(x, y) を返します。
}

上記の例では、パラメータ PointClass の型をコンストラクタとして記述し、その後 Point クラスを渡すことができます。

コンストラクターはオブジェクト形式で記述することもできるため、パラメーター PointClass の型を記述する別の方法もあります。

関数createPoint(
  ポイントクラス: {
    new (x:数値, y:数値): ポイント
  }、
  x: 数値、
  y:数値
):ポイント {
  新しい PointClass(x, y) を返します。
}

上記の書き方によれば、コンストラクタを抽出して別途インターフェースを定義することができ、コードの汎用性が大幅に向上する。

インターフェース PointConstructor {
  new(x:数値, y:数値):ポイント;
}

関数createPoint(
  ポイントクラス: PointConstructor、
  x: 数値、
  y:数値
):ポイント {
  新しい PointClass(x, y) を返します。
}

要約すると、クラス自体の型はコンストラクターであり、別のインターフェイスで表すことができます。

構造タイプの原則

クラスも「構造型原則」に従います。オブジェクトは、クラスのインスタンス構造を満たす限り、クラスと同じ型に属します。

クラス Foo {
  id!:番号;
}

関数 fn(arg:Foo) {
  // ...
}

定数バー = {
  ID:10、
  金額: 100、
};

fn(bar); // 正しい

上記の例では、オブジェクト bar はクラス Foo のインスタンス構造を満たしていますが、追加の属性 amount を持っています。したがって、関数 fn() にパラメータとして渡すことができます。

2 つのクラスのインスタンス構造が同じであれば、2 つのクラスには互換性があり、お互いの使用状況で使用できます。

クラス人 {
  名前: 文字列;
}

クラス顧客{
  名前: 文字列;
}

// 正しい
const cust:Customer = 新しい人();

上記の例では、personCustomer は同じ構造を持つ 2 つのクラスであり、TypeScript はこれらを同じ型として扱うため、型が Customer の場合でも person を使用できます。

ここでコードを変更し、person クラスに属性を追加します。

クラス人 {
  名前: 文字列;
  年齢: 番号;
}

クラス顧客{
  名前: 文字列;
}

// 正しい
const cust:Customer = 新しい人();

上の例では、person クラスは属性 age を追加しますが、これはもはや Customer クラスと同じ構造ではありません。ただし、この場合、TypeScript は依然として personCustomer タイプに属していると信じています。

これは、「構造型原則」によれば、personクラスはname属性を持っていれば、Customer型のインスタンス構造を満たしているため、置き換えることができるからです。逆は機能しません。「Customer」クラスにもう 1 つの属性がある場合、エラーが報告されます。

クラス人 {
  名前: 文字列;
}

クラス顧客{
  名前: 文字列;
  年齢: 番号;
}

// エラーを報告する
const cust:Customer = 新しい人();

上記の例では、person クラスの属性 ageCustomer クラスよりも 1 つ少なく、Customer タイプのインスタンス構造を満たしていないため、エラーが報告されます。 Customer タイプを使用する場合、その age 属性を使用できますが、person クラスにはこの属性がありません。

つまり、クラス A がクラス B の構造を持っている限り、追加のプロパティやメソッドがあっても、TypeScript は A が B の型と互換性があるとみなします。

クラスだけでなく、オブジェクトがクラスのインスタンスと同じ構造を持つ場合、TypeScript はその 2 つを同じ型であるとみなします。

クラス人 {
  名前: 文字列;
}

const obj = { 名前: 'ジョン' };
const p:person = obj;

上の例では、オブジェクト objperson のインスタンスではありませんが、それを変数 p に代入してもエラーは報告されません。属性は同じです。

この状況のた​​め、演算子「instanceof」は、オブジェクトがクラスと同じ型であるかどうかを判断するのには適していません。

obj インスタンスオブ パーソン // false

上記の例では、演算子 instanceof により、変数 obj が Person のインスタンスではないが、両方の型が同じであることが確認されます。

空のクラスにはメンバーが含まれておらず、他のクラスは空のクラスと同じ構造を持つと見なされます。したがって、型が空のクラスであれば、すべてのクラス (オブジェクトを含む) を使用できます。

空のクラス {}

関数 fn(x:Empty) {
  // ...
}

fn({});
fn(ウィンドウ);
fn(fn);

上記の例では、関数 fn() のパラメータは空のクラスです。これは、任意のオブジェクトを fn() のパラメータとして使用できることを意味します。

2 つのクラス間の互換性関係を判断するときは、インスタンス メンバーのみがチェックされ、静的メンバーとコンストラクターは考慮されないことに注意してください。

クラスポイント{
  x: 数値。
  y: 数値。
  静的 t: 数値;
  コンストラクター(x:数値) {}
}

クラス位置 {
  x: 数値。
  y: 数値。
  z: 数値。
  コンストラクター(x:文字列) {}
}

const point:Point = 新しい位置('');

上記の例では、PointPosition の静的プロパティとコンストラクターは異なりますが、Point のインスタンス メンバーは Position と同じであるため、PositionPoint と互換性があります。

クラス内にプライベート メンバー (private) または保護されたメンバー (protected) が存在する場合、互換性関係を判断するときに、TypeScript では、プライベート メンバーと保護されたメンバーが同じクラスに由来する必要があります。つまり、2 つのクラスには、継承関係。

// 状況 1
クラスA {
  プライベート名 = 'a';
}

クラス B は A を拡張します {
}

const a:A = 新しい B();

// ケース 2
クラスA {
  保護された名前 = 'a';
}

クラス B は A を拡張します {
  保護された名前 = 'b';
}

const a:A = 新しい B();

上記の例では、AB は両方ともプライベート メンバー (または保護されたメンバー) name を持っています。この場合、BA を継承する (class B が A を継承する) 場合のみ、B` になります。 「A」とのみ互換性があります。

クラスの継承

クラス (ここでは「サブクラス」とも呼ばれます) は、extends キーワードを使用して、別のクラス (ここでは「基本クラス」とも呼ばれます) のすべてのプロパティとメソッドを継承できます。

クラスA {
  挨拶する() {
    console.log('こんにちは、世界!');
  }
}

クラス B は A を拡張します {
}

const b = 新しい B();
b.greet() // 「こんにちは、世界!」

上記の例では、サブクラス B は基本クラス A を継承しているため、greet() メソッドがあり、クラス内でこのメソッドを再度定義する必要はありません。

構造型の原則に従って、型が基本クラスの場合はサブクラスも使用できます。

const a:A = b;
a.greet()

上の例では、変数 a の型は基本クラスですが、サブクラスのインスタンスに割り当てることができます。

サブクラスは、同じ名前の基本クラスのメソッドをオーバーライドできます。

クラス B は A を拡張します {
  挨拶(名前?:文字列) {
    if (名前 === 未定義) {
      super.greet();
    } それ以外 {
      console.log(`こんにちは、${name}`);
    }
  }
}

上の例では、サブクラス B はメソッド greet() を定義しており、このメソッドは基本クラス A 内の同じ名前のメソッドをオーバーライドします。このうち、パラメータ name を省略した場合は、基底クラス Agreet() メソッドが呼び出されます。 ここでは、super.greet() と記述することが一般的です。基本クラスを参照するための super キーワード。

ただし、サブクラス内の同じ名前のメソッドは、基本クラスの型定義と競合できません。

クラスA {
  挨拶する() {
    console.log('こんにちは、世界!');
  }
}

クラス B は A を拡張します {
  // エラーを報告する
  挨拶(名前:文字列) {
    console.log(`こんにちは、${name}`);
  }
}

上記の例では、サブクラス Bgreet() には name パラメータがあり、基本クラス Agreet() の定義と互換性がないため、エラーが報告されます。

基本クラスに保護されたメンバー (protected 修飾子) が含まれている場合、サブクラスはメンバーのアクセシビリティをパブリック (public 修飾子) に設定することも、保護されたメンバーを変更しないままにすることもできますが、代わりにメンバーをプライベートにすることはできません ( private 修飾子)、詳細については以下を参照してください。

クラスA {
  保護された x: 文字列 = '';
  保護された y: 文字列 = '';
  保護された z: 文字列 = '';
}

クラス B は A を拡張します {
  // 正しい
  パブリック x:string = '';

  // 正しい
  保護された y:string = '';

  // エラーを報告する
  プライベート z: 文字列 = '';
}

上記の例では、サブクラス B が基本クラス A の保護されたメンバーをプライベート メンバーに変更すると、エラーが報告されます。

extends キーワードは必ずしもクラス名の後に続く必要はなく、その型がコンストラクターであれば式であってもよいことに注意してください。

//例1
class MyArray extends Array<number> {}

//例2
クラス MyError はエラー {} を拡張します

//例 3
クラスA {
  挨拶() {
    「A からこんにちは」を返します。
  }
}
クラス B {
  挨拶() {
    「B からこんにちは」を返します。
  }
}

インターフェイス グリーター {
  挨拶(): 文字列;
}

インターフェース GreeterConstructor {
  新しい (): 挨拶者。
}

関数 getGreeterBase():GreeterConstructor {
  戻り Math.random() >= 0.5 ? A : B;
}

class Test extends getGreeterBase() {
  SayHello() {
    console.log(this.greeting());
  }
}

上記の例では、例 1 と例 2 の extends キーワードの後に​​コンストラクタが続き、例 3 の extends キーワードの後に​​は式が続き、実行後に得られるものもコンストラクタです。

キーワードを上書きする

サブクラスが親クラスを継承すると、同じ名前の親クラスのメソッドをオーバーライドできます。

クラスA {
  見せる() {
    // ...
  }
  隠れる() {
    // ...
  }
}
クラス B は A を拡張します {
  見せる() {
    // ...
  }
  隠れる() {
    // ...
  }
}

上の例では、クラス B は独自の show() メソッドと hide() メソッドを定義し、クラス A の同じ名前のメソッドをオーバーライドします。

しかし、他の人のクラスを継承するときに、知らないうちに他の人のメソッドをオーバーライドしてしまうことがあります。これを防ぐために、TypeScript 4.3 では [override キーワード](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-3.html#override-and-the--- noimplicitoverride-flag) を導入しました。 )。

クラス B は A を拡張します {
  show() をオーバーライドする {
    // ...
  }
  オーバーライド Hide() {
    // ...
  }
}

上記の例では、クラス B の show() メソッドと hide() メソッドの前に override キーワードが追加されています。これは、クラス A 内の同じ名前を持つこれら 2 つのメソッドをオーバーライドするという作成者の意図を明確に示しています。このとき、クラス A が独自の show() メソッドと hide() メソッドを定義していないとエラーが報告されます。

ただし、これでも、サブクラスが親クラスと同じ名前のメソッドを誤って上書きしてしまう問題は解決されていません。したがって、TypeScript は別のコンパイル パラメーター noImplicitOverride を提供します。このパラメーターをオンにすると、override キーワードが使用されない限り、サブクラスが親クラスの同じ名前のメソッドをオーバーライドすると、エラーが報告されます。

アクセシビリティ修飾子

クラスの内部メンバーの外部アクセスは、「public」、「private」、「protected」の 3 つのアクセス修飾子によって制御されます。

これら 3 つの修飾子の位置は、属性またはメソッドの先頭に記述されます。

公共

「public」修飾子は、これがパブリック メンバーであり、外部から自由にアクセスできることを示します。

クラス グリーター {
  パブリック挨拶() {
    console.log("こんにちは!");
  }
}

const g = new Greeter();
g.greet();

上記の例では、greet() メソッドの前の public 修飾子は、メソッドがクラスの外部から呼び出せること、つまり外部インスタンスを呼び出すことができることを示しています。

public 修飾子がデフォルトの修飾子です。これを省略すると、実際にはこの修飾子が付けられます。したがって、クラスのプロパティとメソッドはデフォルトで外部からアクセス可能です。

通常の状況では、目を引き、コードを読みやすくするためでない限り、「public」は省略されます。

プライベート

private 修飾子はプライベート メンバーを表し、現在のクラス内でのみ使用できます。クラスのインスタンスもサブクラスもこのメンバーを使用できません。

クラスA {
  プライベート x:数値 = 0;
}

const a = 新しい A();
a.x // エラーレポート

クラス B は A を拡張します {
  showX() {
    console.log(this.x); // エラーレポート
  }
}

上の例では、属性 x の前に private 修飾子が付いており、それがプライベート メンバーであることを示しています。したがって、インスタンス オブジェクトとサブクラスがこのメンバーを使用すると、エラーが報告されます。

サブクラスは、親クラスのプライベート メンバーと同じ名前のメンバーを定義できないことに注意してください。

クラスA {
  プライベート x = 0;
}

クラス B は A を拡張します {
  x = 1; // エラー
}

上記の例では、クラス A にはプライベート プロパティ x があり、サブクラス B は独自のプロパティ x を定義できません。

クラス内の場合、現在のクラスのインスタンスはプライベート メンバーを取得できます。

クラスA {
  プライベート x = 10;

  f(obj:A) {
    コンソール.ログ(obj.x);
  }
}

const a = 新しい A();
a.f(a) // 10

上記の例では、クラス A 内で、A のインスタンス オブジェクトがプライベート メンバー x を取得できます。

厳密に言えば、「private」で定義されるプライベート メンバーは真のプライベート メンバーではありません。一方では、JavaScript にコンパイルした後、「private」キーワードは削除され、外部からメンバーにアクセスするときにエラーは報告されません。一方、前者の理由により、TypeScript は角括弧 ([]) または in 演算子を使用した private メンバーへのアクセスを厳密に禁止していません。インスタンス オブジェクトはメンバーにアクセスできます。

クラスA {
  プライベート x = 1;
}

const a = 新しい A();
a['x'] // 1

if ('x' in a) { // 正しい
  // ...
}

上記の例では、「A」クラスの属性「x」はプライベート プロパティですが、インスタンスが角括弧を使用している場合は、このプロパティを読み取るか、「in」演算子を使用してプロパティが存在するかどうかを確認できます。正しく実行できます。

private に関するこれらの問題と、ES2022 標準がリリースされる前にリリースされたため、ES2022 ではプライベート メンバー #propName を記述する独自の方法が導入されました。したがって、真にプライベートなメンバーを取得するには、private を使用せず、ES2022 記述メソッドを使用することをお勧めします。

クラスA {
  #x = 1;
}

const a = 新しい A();
a['x'] // エラーレポート

上記の例では、ES2022 プライベート メンバーの記述方法 (属性名の前に # を追加) を使用すると、TypeScript はインスタンス オブジェクトに属性 x がないことを正しく認識し、エラーを報告します。

コンストラクターはプライベートにすることもできます。これにより、インスタンス オブジェクトを生成するための「new」コマンドの使用が直接妨げられ、クラス内でのみインスタンス オブジェクトを作成できます。

現時点では通常、ファクトリ関数として機能する静的メソッドがあり、すべてのインスタンスがこのメソッドを通じて強制的に生成されます。

クラス シングルトン {
  プライベート静的インスタンス?: シングルトン;

  プライベート コンストラクター() {}

  静的 getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = 新しい Singleton();
    }
    Singleton.instance を返します。
  }
}

const s = Singleton.getInstance();

上記の例では、プライベート コンストラクターを使用してシングルトン パターンを実装しています。 Singleton のインスタンスを取得するには、「new」コマンドは使用できません。使用できるのは「getInstance()」メソッドのみです。

保護されました

「protected」修飾子は、メンバーが保護されたメンバーであり、インスタンスはそのメンバーを使用できませんが、サブクラス内では使用できることを示します。

クラスA {
  保護された x = 1;
}

クラス B は A を拡張します {
  getX() {
    this.x を返します。
  }
}

const a = 新しい A();
const b = 新しい B();

a.x // エラーレポート
b.getX() // 1

上記の例では、クラス A の属性 x は保護されたメンバーです。このプロパティ (a.x) をインスタンスから直接読み取るとエラーが発生しますが、このプロパティはサブクラス B 内で読み取ることができます。 。

サブクラスは、親クラスの保護されたメンバーを取得するだけでなく、同じ名前のメンバーを定義することもできます。

クラスA {
  保護された x = 1;
}

クラス B は A を拡張します {
  x = 2;
}

上記の例では、サブクラス B が親クラス A のメンバー x を同じ名前で定義しており、親クラスの x は保護されたメンバーであり、サブクラスはそれをパブリック メンバーに変更します。メンバー。クラス Bx 属性の前には修飾子がありません。これは修飾子が public であることに相当し、外部の世界はこの属性を読み取ることができます。

クラスの外側では、インスタンス オブジェクトは保護されたメンバーを読み取ることができませんが、クラスの内側では読み取ることができます。

クラスA {
  保護された x = 1;

  f(obj:A) {
    コンソール.ログ(obj.x);
  }
}

const a = 新しい A();

a.x // エラーレポート
a.f(a) // 1

上の例では、属性 x はクラス A の保護されたメンバーです。クラスの外では、インスタンス オブジェクト a はこの属性を取得できません。ただし、インスタンス オブジェクト a をクラス A に渡すと、a から x を取得できます。

インスタンス属性の短い形式

実際の開発では、多くのインスタンス属性の値がコンストラクター メソッドを通じて渡されます。

クラスポイント{
  x:数値;
  y:数値;

  コンストラクター(x:数値, y:数値) {
    this.x = x;
    this.y = y;
  }
}

上記の例では、属性 xy の値がコンストラクターのパラメーターを通じて渡されます。

この書き方は、同じ属性に対して型を 2 回宣言することと同じです。1 回目はクラスの先頭で、もう 1 回目はコンストラクターのパラメーターで宣言します。これは少し面倒なので、TypeScript には省略形が用意されています。

クラスポイント{
  コンストラクタ(
    パブリック x:数値、
    パブリック y:番号
  ) {}
}

const p = 新しいポイント(10, 10);
p.x // 10
年 // 10

上記の例では、コンストラクター パラメーター x の前に public 修飾子が付いています。このとき、TypeScript はコンストラクター内にコードを記述することなく、自動的にパブリック プロパティ x を宣言します。 x` も設定されます。値はコンストラクター メソッドのパラメーター値です。ここでの「public」は省略できないことに注意してください。

public 修飾子に加えて、コンストラクターのパラメータ名に privateprotected、または readonly 修飾子が含まれている限り、対応する修飾子のインスタンス プロパティが自動的に宣言されます。

クラスA {
  コンストラクタ(
    公開 a: 数値、
    保護された b: 番号、
    プライベート c: 数値、
    読み取り専用 d:番号
  ) {}
}

// 結果をコンパイルする
クラスA {
    a;
    b;
    c;
    d;
    コンストラクター(a, b, c, d) {
      this.a = a;
      this.b = b;
      this.c = c;
      this.d = d;
    }
}

上記の例では、コンパイル結果から、コンストラクター メソッドの abc、および d が対応するインスタンス属性を生成することがわかります。

「readonly」は、他の 3 つのアクセシビリティ修飾子と一緒に使用することもできます。

クラスA {
  コンストラクタ(
    パブリック読み取り専用 x:数値、
    保護された読み取り専用 y:番号、
    プライベート読み取り専用 z:number
  ) {}
}

最上位の属性を処理する方法

クラスのトップレベルのプロパティに関して、TypeScript の初期の処理方法は、後の ES2022 標準と矛盾しています。これにより、一部のコードの実行が異なります。

クラスのトップレベルのプロパティは、TypeScript で 2 つの方法で記述されます。

クラス ユーザー {
  //書き方その1
  年齢 = 25;

  //書き方2
  コンストラクター(プライベート current Year: 数値) {}
}

上記の例では、1 つ目の書き方はインスタンス属性 age を直接宣言して初期化する方法で、2 つ目はコンストラクタのパラメータ currentyear をインスタンス属性として短縮形で直接宣言する方法です。最上位の属性。

TypeScript の初期のアプローチは、プロパティを初期化せずにトップレベルで宣言し、すべての初期化が完了する前にコンストラクターが実行されるまで待つことでした。

クラス ユーザー {
  年齢 = 25;
}

// TypeScript の初期処理メソッド
クラス ユーザー {
  年齢: 番号;

  コンストラクター() {
    this.age = 25;
  }
}

上記の例では、TypeScript はトップレベルのプロパティ age を早い段階で宣言し、コンストラクターの実行時にそれを 25 に初期化します。

ES2022 標準の方法では、最初にトップレベルのプロパティを初期化し、次にコンストラクターを実行します。これにより、場合によっては、TypeScript と JavaScript で同じコードを実行すると、一貫性のない結果が生じる可能性があります。

この矛盾は通常、2 つの状況で発生します。最初のケースは、トップレベルのプロパティの初期化が他のインスタンスのプロパティに依存する場合です。

クラス ユーザー {
  年齢 = this.current Year - 1998;

  コンストラクター(プライベート current Year: 数値) {
    // 出力結果が不一致になります
    console.log('現在の年齢:', this.age);
  }
}

const user = 新しいユーザー(2023);

上記の例では、最上位プロパティ age の初期化値は、インスタンス プロパティ this.currentyear に依存します。 TypeScriptの処理方法によれば、コンストラクタ内で初期化が完了し、出力結果は25となります。ただし、ES2022 の標準的な処理方法では、最上位のプロパティが宣言された時点で初期化が完了するため、この時点ではまだ this.current Year が unknown に等しいため、 age の初期化結果は となります。 NaN なので、最終出力も NaN になります。

2 番目のケースは、クラスの継承に関連しており、サブクラスによって宣言されたトップレベルのプロパティは親クラスで初期化されます。

インターフェース 動物 {
  動物のもの: 任意;
}

インターフェース Dog 拡張 Animal {
  犬のもの: 任意;
}

クラスAnimalHouse {
  居住者: 動物。

  コンストラクター(動物:動物) {
    this.resident = 動物;
  }
}

class DogHouse extends AnimalHouse {
  住人:犬。

  コンストラクター(犬:犬) {
    スーパー(犬);
  }
}

上の例では、クラス DogHouseAnimalHouse から継承しています。最上位プロパティ resident を宣言しますが、このプロパティの初期化は親クラス AnimalHouse で行われます。異なる設定で以下のコードを実行すると、結果は矛盾します。

const 犬 = {
  動物の内容: '動物',
  犬の内容: '犬'
};

const DogHouse = 新しい DogHouse(犬);

console.log(dogHouse.resident) //出力結果が矛盾します

上記の例では、TypeScript の処理メソッドにより resident プロパティが初期化されるため、パラメーター オブジェクトの値が出力されます。ただし、ES2022 の標準的な方法では、コンストラクターが実行される前にトップレベルのプロパティが初期化されます。これにより、resident プロパティに値が割り当てられなくなり、出力は unknown になります。

この問題を解決し、以前のコードの一貫した動作を保証するために、TypeScript はバージョン 3.7 からコンパイル設定 useDefineForClassFields を導入しました。この設定が「true」に設定されている場合は、ES2022 標準の処理メソッドが使用されます。それ以外の場合は、初期の TypeScript 処理メソッドが使用されます。

そのデフォルト値は target 属性に関連しており、出力ターゲットが ES2022 以降に設定されている場合、useDefineForClassFields のデフォルト値は true になり、それ以外の場合は false になります。この設定の詳細な手順については、公式 3.7 バージョン リリース ノート を参照してください。 -the-declare-property-modifier)。

この不一致を回避し、異なる設定でもコードが同じように動作するようにしたい場合は、すべてのトップレベル プロパティの初期化をコンストラクターに含めることができます。

クラス ユーザー {
  年齢: 番号;

  コンストラクター(プライベート current Year: 数値) {
    this.age = this.current Year - 1998;
    console.log('現在の年齢:', this.age);
  }
}

const user = 新しいユーザー(2023);

上記の例では、最上位属性 age の初期化がコンストラクターに配置されているため、コードの動作はどのような状況でも一貫しています。

クラス継承の場合は、「declare」コマンドを使用してサブクラスの最上位プロパティの型を宣言し、これらのプロパティの初期化が親クラスによって実装されることを TypeScript に伝えるという別の解決策があります。

class DogHouse extends AnimalHouse {
  居住者を宣言: 犬;

  コンストラクター(犬:犬) {
    スーパー(犬);
  }
}

上の例では、resident 属性の型宣言の前に declare コマンドが置かれています。この場合、このコード行は JavaScript へのコンパイル後に存在しないため、矛盾した動作は発生せず、useDefineForClassFields が設定されているかどうかに関係なく、出力結果は同じになります。

静的メンバー

クラス内で static キーワードを使用して静的メンバーを定義できます。

静的メンバーは、インスタンス オブジェクトではなく、クラス自体を通じてのみ使用できるメンバーです。

クラス MyClass {
  静的 x = 0;
  静的 printX() {
    console.log(MyClass.x);
  }
}

MyClass.x // 0
MyClass.printX() // 0

上の例では、「x」は静的プロパティ、「printX()」は静的メソッドです。これらは「MyClass」を通じて取得する必要があり、インスタンス オブジェクトを通じて呼び出すことはできません。

public、private、および protected 修飾子は、「static」キーワードの前に使用できます。

クラス MyClass {
  プライベート静的 x = 0;
}

MyClass.x // エラーレポート

上の例では、静的プロパティ x の前に private 修飾子が付いています。これは、このプロパティが外部から呼び出された場合、エラーが報告されることを意味します。

静的プライベート プロパティは、ES6 構文の # プレフィックスによって表すこともできます。上記の例は次のように書き換えることができます。

クラス MyClass {
  静的 #x = 0;
}

「public」および「protected」の静的メンバーは継承できます。

クラスA {
  パブリック静的 x = 1;
  保護された静的 y = 1;
}

クラス B は A を拡張します {
  静的 getY() {
    y までに戻ります。
  }
}

B.x // 1
B.getY() // 1

上記の例では、クラス A の静的プロパティ xyB によって継承され、パブリック メンバー xB の外部で取得でき、プロテクト メンバー yB の外部でのみ取得できます。 「B」内部取得から取得できます。

汎用クラス

クラスは、型パラメーターを使用してジェネリックとして記述することもできます。ジェネリックの詳細については、「ジェネリック」の章を参照してください。

クラスボックス<タイプ> {
  内容: タイプ;

  コンストラクター(値:型) {
    this.contents = 値;
  }
}

const b:Box<string> = new Box('hello!');

上記の例では、クラス Box は型パラメータ Type を持っているため、ジェネリック クラスです。新しいインスタンスを作成する場合、変数の型宣言には型パラメータの値が含まれる必要がありますが、この例の等号の左側にある Box<string> は推測できるため省略できます。等号の右側から。

静的メンバーではジェネリック型パラメーターを使用できないことに注意してください。

クラスボックス<タイプ> {
  staticdefaultContents: // エラーレポート
}

上記の例では、静的属性 defaultContents の型を型パラメータ Type として記述するとエラーが報告されます。これは、呼び出し時に type パラメーターを指定する (つまり、Box<string>.defaultContents として記述する) 必要があることを意味し、type パラメーターが変更されると、この属性も変更されるため、これは良い習慣ではありません。

抽象クラス、抽象メンバー

TypeScript では、クラスの定義の前にキーワード「abstract」を追加できます。これは、そのクラスがインスタンス化できず、他のクラスのテンプレートとしてのみ使用できることを示します。このようなクラスを「抽象クラス」と呼びます。

抽象クラス A {
  ID = 1;
}

const a = new A(); // エラー

上記の例では、抽象クラスのインスタンスを直接作成するとエラーが報告されます。

抽象クラスは、それに基づいてサブクラスを定義するための基本クラスとしてのみ使用できます。

抽象クラス A {
  ID = 1;
}

クラス B は A を拡張します {
  金額 = 100;
}

const b = 新しい B();

b.id // 1
b.量 // 100

上記の例では、「A」は抽象クラス、「B」は「A」のサブクラスで、「A」のすべてのメンバーを継承し、独自のメンバーを定義してインスタンス化できます。

抽象クラスのサブクラスも抽象クラスになることができます。つまり、抽象クラスは他の抽象クラスを継承できます。

抽象クラス A {
  foo:数値;
}

抽象クラス B は A を拡張します {
  バー:文字列;
}

抽象クラスには、その内部に既に実装されたプロパティとメソッドを含めることも、まだ実装していないプロパティとメソッドを含めることもできます。後者は「抽象メンバー」と呼ばれます。つまり、属性名とメソッド名に「abstract」キーワードが含まれており、メソッドがサブクラスによって実装される必要があることを示します。サブクラスが抽象メンバーを実装していない場合、エラーが報告されます。

抽象クラス A {
  抽象 foo:文字列;
  バー:文字列 = '';
}

クラス B は A を拡張します {
  foo = 'b';
}

上記の例では、抽象クラス A は抽象属性 foo を定義しており、サブクラス B はこの属性を実装する必要があります。実装しない場合はエラーが報告されます。

以下は抽象メソッドの例です。抽象クラスのメソッドの前に「abstract」がある場合、サブクラスがメソッドの実装を提供する必要があることを示します。

抽象クラス A {
  抽象実行():文字列;
}

クラス B は A を拡張します {
  実行する() {
    `B が実行されました` を返します。
  }
}

ここで注意すべき点がいくつかあります。

(1) 抽象メンバーは、通常のクラスではなく、抽象クラスにのみ存在できます。

(2) 抽象メンバは特定の実装コードを持つことができません。つまり、すでに実装されているメンバーの前に「abstract」キーワードを追加することはできません。

(3) 抽象メンバーの前に「private」修飾子を置くことはできません。そうしないと、メンバーをサブクラスに実装できません。

(4) サブクラスは、最大 1 つの抽象クラスのみを継承できます。

つまり、抽象クラスの役割は、関連するさまざまなサブクラスが、テンプレートとみなせる基本クラスと同じインターフェイスを持つようにすることです。抽象メンバーはサブクラスによって実装される必要があるメンバーですが、非抽象メンバーは基本クラスによって実装され、すべてのサブクラスによって共有されるメンバーを表します。

この質問

クラス メソッドは多くの場合、メソッドが現在存在するオブジェクトを表す this キーワードを使用します。

クラスA {
  名前 = 'A';

  getName() {
    this.name を返します。
  }
}

const a = 新しい A();
a.getName() // 'A'

const b = {
  名前:「b」、
  getName: a.getName
};
b.getName() // 'b'

上記の例では、変数 a と b の getName() は同じメソッドですが、実行結果が異なります。これは、内部の this が異なるオブジェクトを指しているためです。 「getName()」が変数「a」に対して演算される場合、「this」は「a」を指します。「b」に対して演算される場合、「this」は「b」を指します。

場合によっては、「this」タイプを指定する必要がありますが、通常、JavaScript 関数には「this」パラメータがありません。この場合、TypeScript では、関数の最初に配置される「this」という名前のパラメータを追加できます。関数を説明するパラメータリスト。内部の「this」キーワードのタイプ。

// コンパイル前
関数 fn(
  これ: SomeType、
  x: 数値
) {
  /* ... */
}

//コンパイル後
関数 fn(x) {
  /* ... */
}

上記の例では、関数 fn() の最初のパラメータは this であり、これは関数内で this の型を宣言するために使用されます。コンパイル時に、TypeScript は関数の最初のパラメーターの名前が「this」であることを検出すると、このパラメーターを削除します。つまり、コンパイル結果にはこのパラメーターは含まれません。

クラスA {
  名前 = 'A';

  getName(this: A) {
    this.name を返します。
  }
}

const a = 新しい A();
const b = a.getName;

b() // エラーを報告する

上記の例では、クラス AgetName()this パラメータが追加されています。このメソッドを直接呼び出すと、this の型が宣言された型と一致せず、エラーが発生します。

「this」パラメータの型は、さまざまなオブジェクトとして宣言できます。

関数 foo(
  これ: { 名前: 文字列 }
) {
  this.name = 'ジャック';
  this.name = 0; // エラーレポート
}

foo.call({ name: 123 }); // エラーを報告する

上記の例では、パラメータ this の型は name 属性を持つオブジェクトです。 this がこの条件を満たさない場合、エラーが報告されます。

TypeScript には、「noImplicitThis」コンパイル オプションが用意されています。この設定がオンになっている場合、「this」の値が「any」タイプであると推測されると、エラーが報告されます。

// noImplicitThis が開きます

クラス長方形{
  コンストラクタ(
    パブリック幅:数値、
    公開高さ:数値
  ) {}

  getAreaFunction() {
    戻り関数 () {
      return this.width * this.height; // エラーレポート
    };
  }
}

上記の例では、getAreaFunction() メソッドが関数を返し、この関数内で this が使用されていますが、この thisRectangle クラスとは何の関係もありません。その型は any として推測されます。 , そのため、エラーが報告されます。

クラス内では、「this」自体を現在のクラスのインスタンス オブジェクトを表す型として使用することもできます。

クラスボックス{
  内容:文字列 = '';

  set(値:文字列):this {
    this.contents = 値;
    これを返します。
  }
}

上の例では、set() メソッドの戻り値の型は this で、現在のインスタンス オブジェクトを表します。

「this」タイプは静的メンバーに適用できないことに注意してください。

クラスA {
  static a:this; // エラーを報告します
}

上記の例では、静的プロパティ a の戻り値の型は this であり、エラーが報告されます。その理由は、「this」型はインスタンス オブジェクトを表しますが、静的メンバーはインスタンス オブジェクトを取得できないためです。

一部のメソッドは、現在の「this」が特定のタイプに属しているかどうかを示すブール値を返します。このとき、これらのメソッドの戻り値の型は、is演算子を使用したthis is Typeという形式で記述することができます。

クラス FileSystemObject {
  isFile(): これは FileRep {
    FileRep のこのインスタンスを返します。
  }

  isDirectory(): これはディレクトリです {
    このディレクトリのインスタンスを返します。
  }

  // ...
}

上記の例では、両方のメソッドの戻り値の型は、戻り値を正確に表すことができる「this is Type」の形式で記述されたブール値です。 「is」演算子の概要については、「型アサーション」の章を参照してください。

参考リンク


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0