このページは非推奨になりました

このハンドブックのページは置き換えられました。新しいページへ移動

クラス

従来のJavaScriptは、関数とプロトタイプベースの継承を使用して再利用可能なコンポーネントを構築しますが、これはオブジェクト指向のアプローチに慣れているプログラマにとっては少しぎこちなく感じるかもしれません。オブジェクト指向のアプローチでは、クラスが機能を継承し、オブジェクトはこれらのクラスから構築されます。ECMAScript 2015(ECMAScript 6とも呼ばれる)以降、JavaScriptプログラマは、このオブジェクト指向のクラスベースのアプローチを使用してアプリケーションを構築できるようになりました。TypeScriptでは、開発者がこれらのテクニックをすぐに使用し、主要なすべてのブラウザとプラットフォームで動作するJavaScriptにコンパイルすることを可能にします。次のバージョンのJavaScriptを待つ必要はありません。

クラス

簡単なクラスベースの例を見てみましょう。

ts
class Greeter {
greeting: string;
 
constructor(message: string) {
this.greeting = message;
}
 
greet() {
return "Hello, " + this.greeting;
}
}
 
let greeter = new Greeter("world");
Try

以前C#またはJavaを使用していたことがある場合は、構文がなじみがあるはずです。新しいクラス`Greeter`を宣言します。このクラスには、`greeting`というプロパティ、コンストラクタ、`greet`メソッドの3つのメンバーがあります。

クラス内でクラスのメンバーを参照する際には、`this.`を前に付けることに注意してください。これはメンバーアクセスであることを示しています。

最後の行では、`new`を使用して`Greeter`クラスのインスタンスを構築します。これにより、以前に定義したコンストラクタが呼び出され、`Greeter`の形状を持つ新しいオブジェクトが作成され、コンストラクタが実行されて初期化されます。

継承

TypeScriptでは、一般的なオブジェクト指向パターンを使用できます。クラスベースのプログラミングにおける最も基本的なパターンの1つは、継承を使用して既存のクラスを拡張して新しいクラスを作成できることです。

例を見てみましょう。

ts
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
 
class Dog extends Animal {
bark() {
console.log("Woof! Woof!");
}
}
 
const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
Try

この例は、最も基本的な継承機能を示しています。クラスは、基底クラスからプロパティとメソッドを継承します。ここで、`Dog`は、`extends`キーワードを使用して`Animal`基底クラスから派生する*派生*クラスです。派生クラスはしばしば*サブクラス*と呼ばれ、基底クラスはしばしば*スーパークラス*と呼ばれます。

`Dog`は`Animal`からの機能を継承しているため、`bark()`と`move()`の両方ができる`Dog`のインスタンスを作成できました。

より複雑な例を見てみましょう。

ts
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
 
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
 
class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
 
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
 
sam.move();
tom.move(34);
Try

この例では、これまで説明しなかった他のいくつかの機能について説明します。再び、`extends`キーワードを使用して`Animal`の2つの新しいサブクラスである`Horse`と`Snake`を作成します。

以前の例との違いの1つは、コンストラクタ関数を含む派生クラスは必ず`super()`を呼び出す必要があることです。これにより、基底クラスのコンストラクタが実行されます。さらに、コンストラクタの本体で`this`のプロパティにアクセスする前に、必ず`super()`を呼び出す必要があります。これはTypeScriptが強制する重要なルールです。

この例は、基底クラスのメソッドをサブクラス用に特化したメソッドでオーバーライドする方法も示しています。`Snake`と`Horse`の両方が、`Animal`の`move`メソッドをオーバーライドする`move`メソッドを作成し、各クラスに固有の機能を提供しています。`tom`は`Animal`として宣言されていますが、その値は`Horse`であるため、`tom.move(34)`を呼び出すと、`Horse`のオーバーライドメソッドが呼び出されます。

Slithering... Sammy the Python moved 5m. Galloping... Tommy the Palomino moved 34m.

public、private、protected修飾子

デフォルトで public

これまでの例では、プログラム全体で宣言したメンバに自由にアクセスできました。他の言語のクラスに精通している場合、上記の例ではこれを達成するために `public` という単語を使用する必要がなかったことに気づいたかもしれません。たとえば、C#では、各メンバを明示的に `public` とラベル付けしないと可視になりません。TypeScriptでは、各メンバはデフォルトで `public` です。

メンバを明示的に `public` とマークすることもできます。前のセクションの `Animal` クラスを次のように記述することもできました。

ts
class Animal {
public name: string;
 
public constructor(theName: string) {
this.name = theName;
}
 
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
Try

ECMAScript プライベートフィールド

TypeScript 3.8 では、プライベートフィールドに関する新しい JavaScript 構文がサポートされています。

ts
class Animal {
#name: string;
constructor(theName: string) {
this.#name = theName;
}
}
 
new Animal("Cat").#name;
Property '#name' is not accessible outside class 'Animal' because it has a private identifier.18013Property '#name' is not accessible outside class 'Animal' because it has a private identifier.
Try

この構文は JavaScript ランタイムに組み込まれており、各プライベートフィールドの分離についてより確実な保証を行うことができます。現時点では、これらのプライベートフィールドに関する最良のドキュメントは、TypeScript 3.8 の リリースノート にあります。

TypeScript の `private` の理解

TypeScript は、メンバを `private` としてマークする方法も提供しており、包含クラスの外からはアクセスできません。例:

ts
class Animal {
private name: string;
 
constructor(theName: string) {
this.name = theName;
}
}
 
new Animal("Cat").name;
Property 'name' is private and only accessible within class 'Animal'.2341Property 'name' is private and only accessible within class 'Animal'.
Try

TypeScript は構造的型システムです。2 つの異なる型を比較する場合、それらがどこから来たかに関係なく、すべてのメンバの型が互換性がある場合、型自体が互換性があると見なします。

ただし、`private` および `protected` メンバを持つ型を比較する場合、これらの型は異なる方法で扱われます。2 つの型が互換性があると見なされるためには、一方に `private` メンバがある場合、他方にも同じ宣言で生成された `private` メンバが必要です。`protected` メンバについても同様です。

これが実際にはどのように機能するかをよりよく理解するために、例を見てみましょう。

ts
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
 
class Rhino extends Animal {
constructor() {
super("Rhino");
}
}
 
class Employee {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
 
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
 
animal = rhino;
animal = employee;
Type 'Employee' is not assignable to type 'Animal'. Types have separate declarations of a private property 'name'.2322Type 'Employee' is not assignable to type 'Animal'. Types have separate declarations of a private property 'name'.
Try

この例では、`Animal` と `Rhino` があり、`Rhino` は `Animal` のサブクラスです。また、形状が `Animal` と同じに見える新しいクラス `Employee` もあります。これらのクラスのいくつかのインスタンスを作成し、互いに代入しようと試みて、何が起こるかを確認します。`Animal` と `Rhino` は、`Animal` の `private name: string` の同じ宣言から `private` の側面を共有しているため、互換性があります。しかし、`Employee` はそうではありません。`Employee` から `Animal` に代入しようとすると、これらの型は互換性がないというエラーが発生します。`Employee` にも `name` という `private` メンバがありますが、それは `Animal` で宣言したものとは異なります。

protected の理解

protected 修飾子は、`private` 修飾子と非常によく似ていますが、`protected` と宣言されたメンバは、派生クラス内からもアクセスできる点が異なります。例:

ts
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
 
class Employee extends Person {
private department: string;
 
constructor(name: string, department: string) {
super(name);
this.department = department;
}
 
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
 
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name);
Property 'name' is protected and only accessible within class 'Person' and its subclasses.2445Property 'name' is protected and only accessible within class 'Person' and its subclasses.
Try

`Person` の外部では `name` を使用できませんが、`Employee` は `Person` から派生しているため、`Employee` のインスタンスメソッド内では引き続き使用できます。

コンストラクタも `protected` とマークできます。これは、クラスを包含クラスの外でインスタンス化することはできませんが、拡張できることを意味します。例:

ts
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
 
// Employee can extend Person
class Employee extends Person {
private department: string;
 
constructor(name: string, department: string) {
super(name);
this.department = department;
}
 
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
 
let howard = new Employee("Howard", "Sales");
let john = new Person("John");
Constructor of class 'Person' is protected and only accessible within the class declaration.2674Constructor of class 'Person' is protected and only accessible within the class declaration.
Try

Readonly 修飾子

readonly キーワードを使用することで、プロパティを readonly にすることができます。Readonly プロパティは、宣言時またはコンストラクタで初期化する必要があります。

ts
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
 
constructor(theName: string) {
this.name = theName;
}
}
 
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit";
Cannot assign to 'name' because it is a read-only property.2540Cannot assign to 'name' because it is a read-only property.
Try

パラメータプロパティ

最後の例では、`Octopus` クラスで `readonly` メンバ `name` とコンストラクタパラメータ `theName` を宣言する必要がありました。これは、`Octopus` コンストラクタの実行後に `theName` の値にアクセスできるようにするために必要です。*パラメータプロパティ*を使用すると、メンバを1箇所で作成および初期化できます。パラメータプロパティを使用した前の `Octopus` クラスのさらに改訂版を次に示します。

ts
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}
 
let dad = new Octopus("Man with the 8 strong legs");
dad.name;
Try

`theName` を完全に削除し、コンストラクタで短縮された `readonly name: string` パラメータを使用して `name` メンバを作成および初期化する方法に注目してください。宣言と代入を1箇所に統合しました。

パラメータプロパティは、コンストラクタパラメータの前にアクセス修飾子または `readonly`、またはその両方を付けることで宣言されます。パラメータプロパティに `private` を使用すると、プライベートメンバが宣言および初期化されます。同様に、`public`、`protected`、`readonly` についても同じことが行われます。

アクセサ

TypeScript は、ゲッター/セッターをオブジェクトのメンバへのアクセスをインターセプトする方法としてサポートしています。これにより、各オブジェクトのメンバへのアクセス方法をより細かく制御できます。

`get` と `set` を使用するように単純なクラスを変換してみましょう。まず、ゲッターとセッターを使用しない例から始めましょう。

ts
class Employee {
fullName: string;
}
 
let employee = new Employee();
employee.fullName = "Bob Smith";
 
if (employee.fullName) {
console.log(employee.fullName);
}
Try

人々が `fullName` を直接設定できるようにすることは非常に便利ですが、`fullName` が設定されるときにいくつかの制約を適用したい場合もあります。

このバージョンでは、`newName` の長さを確認して、バックエンドデータベースフィールドの最大長と互換性があることを確認するセッターを追加します。互換性がない場合は、エラーをスローして、クライアントコードに何か問題が発生したことを通知します。

既存の機能を維持するために、`fullName` を変更せずに取得する単純なゲッターも追加します。

ts
const fullNameMaxLength = 10;
 
class Employee {
private _fullName: string = "";
 
get fullName(): string {
return this._fullName;
}
 
set fullName(newName: string) {
if (newName && newName.length > fullNameMaxLength) {
throw new Error("fullName has a max length of " + fullNameMaxLength);
}
 
this._fullName = newName;
}
}
 
let employee = new Employee();
employee.fullName = "Bob Smith";
 
if (employee.fullName) {
console.log(employee.fullName);
}
Try

アクセサが値の長さをチェックしていることを確認するために、10文字を超える名前を代入しようと試みて、エラーが発生することを確認できます。

アクセサに関する注意点がいくつかあります。

まず、アクセサを使用するには、コンパイラを ECMAScript 5 以降を出力するように設定する必要があります。ECMAScript 3 へのダウングレードはサポートされていません。第二に、`get` はあり `set` がないアクセサは、自動的に `readonly` と推論されます。これは、コードから `.d.ts` ファイルを生成する場合に役立ちます。なぜなら、プロパティのユーザーは、それを変更できないことがわかるからです。

静的プロパティ

これまでは、クラスの*インスタンス*メンバ、つまりインスタンス化されたときにオブジェクトに表示されるものについてのみ説明してきました。クラスの*静的*メンバ、つまりインスタンスではなくクラス自体に表示されるメンバを作成することもできます。この例では、`static` を起点で使用しています。これは、すべてのグリッドの一般的な値であるためです。各インスタンスは、クラスの名前を前に付けることでこの値にアクセスします。インスタンスへのアクセス前に `this.` を付けるのと同様に、ここでは静的アクセス前に `Grid.` を付けます。

ts
class Grid {
static origin = { x: 0, y: 0 };
 
calculateDistanceFromOrigin(point: { x: number; y: number }) {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
 
constructor(public scale: number) {}
}
 
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
 
console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));
Try

抽象クラス

抽象クラスは、他のクラスを派生させるための基底クラスです。直接インスタンス化することはできません。インターフェースとは異なり、抽象クラスにはメンバーの実装の詳細を含めることができます。`abstract`キーワードは、抽象クラスと、抽象クラス内の抽象メソッドを定義するために使用されます。

ts
abstract class Animal {
abstract makeSound(): void;
 
move(): void {
console.log("roaming the earth...");
}
}
Try

抽象としてマークされた抽象クラス内のメソッドには実装が含まれておらず、派生クラスで実装する必要があります。抽象メソッドは、インターフェースメソッドと同様の構文を共有します。どちらもメソッド本体を含めずにメソッドのシグネチャを定義します。ただし、抽象メソッドには`abstract`キーワードを含める必要があり、アクセス修飾子を含めることもできます。

ts
abstract class Department {
constructor(public name: string) {}
 
printName(): void {
console.log("Department name: " + this.name);
}
 
abstract printMeeting(): void; // must be implemented in derived classes
}
 
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call super()
}
 
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
 
generateReports(): void {
console.log("Generating accounting reports...");
}
}
 
let department: Department; // ok to create a reference to an abstract type
department = new Department(); // error: cannot create an instance of an abstract class
Cannot create an instance of an abstract class.2511Cannot create an instance of an abstract class.
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
department.generateReports(); // error: department is not of type AccountingDepartment, cannot access generateReports
Property 'generateReports' does not exist on type 'Department'.2339Property 'generateReports' does not exist on type 'Department'.
Try

高度なテクニック

コンストラクタ関数

TypeScriptでクラスを宣言すると、実際には同時に複数の宣言を作成しています。最初のものは、クラスのインスタンスの型です。

ts
class Greeter {
greeting: string;
 
constructor(message: string) {
this.greeting = message;
}
 
greet() {
return "Hello, " + this.greeting;
}
}
 
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"
Try

ここで、`let greeter: Greeter`と述べるとき、`Greeter`をクラス`Greeter`のインスタンスの型として使用しています。これは、他のオブジェクト指向言語からのプログラマーにとってほぼ当たり前のことです。

また、コンストラクタ関数と呼ばれる別の値も作成しています。これは、クラスのインスタンスを`new`で作成する際に呼び出される関数です。これが実際にはどのようなものかを見るために、上記の例によって作成されたJavaScriptを見てみましょう。

ts
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
 
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
 
return Greeter;
})();
 
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"
Try

ここで、`let Greeter`にはコンストラクタ関数が割り当てられます。`new`を呼び出してこの関数を実行すると、クラスのインスタンスが取得されます。コンストラクタ関数には、クラスのすべての静的メンバーも含まれています。各クラスを別の方法で考えるもう一つの方法は、インスタンス側と静的側の2つがあるということです。

この違いを示すために、例を少し変更してみましょう。

ts
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}
 
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet()); // "Hello, there"
 
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
 
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet()); // "Hey there!"
 
let greeter3: Greeter;
greeter3 = new Greeter();
console.log(greeter3.greet()); // "Hey there!"
Try

この例では、`greeter1`は以前と同様に機能します。`Greeter`クラスをインスタンス化し、このオブジェクトを使用します。これはこれまで見てきたものです。

次に、クラスを直接使用します。ここでは、`greeterMaker`という新しい変数を作成します。この変数にはクラス自体、つまりコンストラクタ関数が格納されます。ここでは`typeof Greeter`を使用します。つまり、「`Greeter`クラス自体の型をください」という意味であり、インスタンス型ではありません。「`Greeter`と呼ばれるシンボルの型をください」という意味、より正確にはコンストラクタ関数の型です。この型には、`Greeter`クラスのインスタンスを作成するコンストラクタとともに、`Greeter`のすべての静的メンバーが含まれます。これは、`greeterMaker`に対して`new`を使用し、`Greeter`の新しいインスタンスを作成して、以前のように呼び出すことで示しています。静的プロパティの変更は好ましくないことも言及しておきましょう。ここでは、`greeter3`の`standardGreeting`が`「Hello, there」`ではなく`「Hey there!」`になっています。

クラスをインターフェースとして使用

前のセクションで述べたように、クラス宣言は2つのものを作成します。クラスのインスタンスを表す型と、コンストラクタ関数です。クラスは型を作成するため、インターフェースを使用できる場所と同じ場所でクラスを使用できます。

ts
class Point {
x: number;
y: number;
}
 
interface Point3d extends Point {
z: number;
}
 
let point3d: Point3d = { x: 1, y: 2, z: 3 };
Try

TypeScript ドキュメントはオープンソースプロジェクトです。Pull Request を送信してこれらのページの改善にご協力ください by sending a Pull Request

このページへの貢献者
RCRyan Cavanaugh (53)
DRDaniel Rosenwasser (27)
OTOrta Therox (21)
NSNathan Shively-Sanders (8)
BWBrice Wilson (5)
25+

最終更新日:2024年3月21日