TypeScriptでクラスを使用する方法
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
クラスは、オブジェクト指向プログラミング(OOP)言語で使用される一般的な抽象化であり、オブジェクトと呼ばれるデータ構造を記述します。 これらのオブジェクトには初期状態が含まれ、その特定のオブジェクトインスタンスにバインドされた動作を実装する場合があります。 2015年、 ECMAScript 6 は、 JavaScript に新しい構文を導入して、言語のプロトタイプ機能を内部的に使用するクラスを作成しました。 TypeScriptはその構文を完全にサポートしており、メンバーの可視性、抽象クラス、ジェネリッククラス、矢印関数メソッドなどの機能も追加しています。
このチュートリアルでは、クラスの作成に使用される構文、使用可能なさまざまな機能、およびコンパイル時の型チェック中にTypeScriptでクラスがどのように扱われるかについて説明します。 さまざまなコードサンプルを使用した例を紹介します。これは、独自のTypeScript環境で実行できます。
前提条件
このチュートリアルに従うには:
- TypeScriptプログラムを実行して、例に従うことができる環境。 これをローカルマシンに設定するには、次のものが必要です。
TypeScript関連のパッケージを処理する開発環境を実行するためにインストールされたNodeとnpm(またはyarn)の両方。 このチュートリアルは、Node.jsバージョン14.3.0およびnpmバージョン6.14.5でテストされました。 macOSまたはUbuntu18.04にインストールするには、「Node.jsをインストールしてmacOSにローカル開発環境を作成する方法」または「Ubuntu18.04にNode.jsをインストールする方法」の「PPAを使用したインストール」セクションの手順に従います。 これは、Windows Subsystem for Linux(WSL)を使用している場合にも機能します。 さらに、TypeScriptコンパイラ(tsc)がマシンにインストールされている必要があります。 これを行うには、TypeScriptの公式Webサイトを参照してください。 - ローカルマシン上にTypeScript環境を作成したくない場合は、公式の TypeScriptPlaygroundを使用してフォローできます。
- JavaScript、特に destructuring、rest演算子、 imports /exportsなどのES6+構文に関する十分な知識が必要です。 これらのトピックに関する詳細情報が必要な場合は、JavaScriptシリーズのコーディング方法を読むことをお勧めします。
- このチュートリアルでは、TypeScriptをサポートし、インラインエラーを表示するテキストエディタの側面を参照します。 これはTypeScriptを使用するために必要ではありませんが、TypeScript機能をさらに活用します。 これらの利点を活用するには、 Visual Studio Code のようなテキストエディターを使用できます。このエディターは、TypeScriptをすぐにサポートします。 TypeScriptPlaygroundでこれらの利点を試すこともできます。
このチュートリアルに示されているすべての例は、TypeScriptバージョン4.3.2を使用して作成されています。
TypeScriptでクラスを作成する
このセクションでは、TypeScriptでクラスを作成するために使用される構文の例を実行します。 TypeScriptを使用してクラスを作成する基本的な側面のいくつかについて説明しますが、構文は、JavaScriptを使用してクラスを作成するために使用されるものとほとんど同じです。 このため、このチュートリアルでは、TypeScriptで使用できるいくつかの特徴的な機能に焦点を当てます。
次のコードに示すように、class
キーワード、クラス名、{}
ペアブロックの順に使用して、クラス宣言を作成できます。
class Person {
}
このスニペットは、Person
という名前の新しいクラスを作成します。 次に、new
キーワード、クラス名、空のパラメータリスト(省略可能)を使用して、Person
クラスの新しいインスタンスを作成できます。 )、次の強調表示されたコードに示されているように:
class Person {
}
const personInstance = new Person();
クラス自体は、指定された形状のオブジェクトを作成するための青写真と考えることができますが、インスタンスは、この青写真から作成されたオブジェクト自体です。
クラスを操作する場合、ほとんどの場合、constructor
関数を作成する必要があります。 constructor
は、クラスの新しいインスタンスが作成されるたびに実行されるメソッドです。 これは、クラスの値を初期化するために使用できます。
Person
クラスにコンストラクターを導入します。
class Person {
constructor() {
console.log("Constructor called");
}
}
const personInstance = new Person();
このコンストラクターは、personInstance
が作成されると、Constructor called
をコンソールに記録します。
コンストラクターは、パラメーターを受け入れるという点で通常の関数に似ています。 これらのパラメーターは、クラスの新しいインスタンスを作成するときにコンストラクターに渡されます。 現在、クラスのインスタンスを作成するときに空のパラメーターリスト()
で示されているように、コンストラクターにパラメーターを渡していません。
次に、タイプstring
のname
という新しいパラメーターを導入します。
class Person {
constructor(name: string) {
console.log(`Constructor called with name=${name}`);
}
}
const personInstance = new Person("Jane");
強調表示されたコードで、タイプstring
のname
というパラメーターをクラスコンストラクターに追加しました。 次に、Person
クラスの新しいインスタンスを作成するときに、そのパラメーターの値(この場合は文字列"Jane"
)も設定します。 最後に、console.log
を変更して、引数を画面に出力しました。
このコードを実行すると、ターミナルに次の出力が表示されます。
OutputConstructor called with name=Jane
ここでは、コンストラクターのパラメーターはオプションではありません。 これは、クラスをインスタンス化するときに、name
パラメーターをコンストラクターに渡す必要があることを意味します。 次の例のように、name
パラメーターをコンストラクターに渡さない場合。
const unknownPerson = new Person;
TypeScriptコンパイラはエラー2554
を出します:
OutputExpected 1 arguments, but got 0. (2554)
filename.ts(4, 15): An argument for 'name' was not provided.
TypeScriptでクラスを宣言したので、次にプロパティを追加してそれらのクラスを操作します。
クラスプロパティの追加
クラスの最も有用な側面の1つは、クラスから作成された各インスタンスの内部にあるデータを保持する機能です。 これは、プロパティを使用して行われます。
TypeScriptには、このプロセスをJavaScriptクラスと区別するいくつかの安全性チェックがあります。たとえば、プロパティを初期化してundefined
にならないようにする必要があります。 このセクションでは、これらの安全性チェックを説明するために、クラスに新しいプロパティを追加します。
TypeScriptでは、通常、クラスの本体で最初にプロパティを宣言し、それに型を指定する必要があります。 たとえば、name
プロパティをPerson
クラスに追加します。
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
この例では、constructor
でプロパティを設定することに加えて、タイプstring
でプロパティname
を宣言します。
注: TypeScriptでは、クラス内のプロパティの可視性を宣言して、データにアクセスできる場所を決定することもできます。 name: string
宣言では、可視性は宣言されていません。つまり、プロパティは、どこからでもアクセスできるデフォルトのpublic
ステータスを使用します。 可視性を明示的に制御したい場合は、プロパティでこれを宣言します。 これについては、チュートリアルの後半で詳しく説明します。
プロパティにデフォルト値を指定することもできます。 例として、instantiatedAt
という新しいプロパティを追加します。このプロパティは、クラスインスタンスがインスタンス化された時刻に設定されます。
class Person {
name: string;
instantiatedAt = new Date();
constructor(name: string) {
this.name = name;
}
}
これは、 Dateオブジェクトを使用して、インスタンスの作成の初期日付を設定します。 このコードが機能するのは、次のように、クラスコンストラクターが呼び出されたときにデフォルト値のコードが実行されるためです。これは、コンストラクターに値を設定するのと同じです。
class Person {
name: string;
instantiatedAt: Date;
constructor(name: string) {
this.name = name;
this.instantiatedAt = new Date();
}
}
クラスの本体でデフォルト値を宣言することにより、コンストラクターで値を設定する必要はありません。
クラス内のプロパティにタイプを設定する場合は、そのプロパティもそのタイプの値に初期化する必要があることに注意してください。 これを説明するために、次のコードのように、クラスプロパティを宣言しますが、初期化子を提供しないでください。
class Person {
name: string;
instantiatedAt: Date;
constructor(name: string) {
this.name = name;
}
}
instantiatedAt
にはDate
のタイプが割り当てられているため、常にDate
オブジェクトである必要があります。 ただし、初期化がないため、クラスがインスタンス化されると、プロパティはundefined
になります。 このため、TypeScriptコンパイラはエラー2564
を表示します。
OutputProperty 'instantiatedAt' has no initializer and is not definitely assigned in the constructor. (2564)
これは、クラスのインスタンス化時に正しいプロパティが存在することを確認するための追加のTypeScript安全性チェックです。
TypeScriptには、コンストラクターに渡されるパラメーターと同じ名前のプロパティを作成するためのショートカットもあります。 このショートカットは、パラメータプロパティと呼ばれます。
前の例では、name
プロパティを、クラスコンストラクターに渡されたname
パラメーターの値に設定しました。 クラスにフィールドを追加すると、これを書くのが面倒になる可能性があります。 たとえば、タイプnumber
のage
という新しいフィールドをPerson
クラスに追加し、コンストラクターにも追加します。
class Person {
name: string;
age: number;
instantiatedAt = new Date();
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
これは機能しますが、TypeScriptは、パラメータープロパティ、またはコンストラクターのパラメーターに設定されたプロパティを使用して、このような定型コードを減らすことができます。
class Person {
instantiatedAt = new Date();
constructor(
public name: string,
public age: number
) {}
}
このスニペットでは、name
およびage
プロパティ宣言をクラス本体から削除し、コンストラクターのパラメーターリスト内に移動しました。 これを行うと、これらのコンストラクターパラメーターもそのクラスのプロパティであることがTypeScriptに通知されます。 このように、以前のように、クラスのプロパティをコンストラクターで受け取ったパラメーターの値に設定する必要はありません。
注:可視性修飾子public
がコードに明示的に記述されていることに注意してください。 この修飾子は、パラメータープロパティを設定するときに含める必要があり、public
の可視性に自動的にデフォルト設定されることはありません。
TypeScriptコンパイラによって発行されたコンパイル済みJavaScriptを見ると、このコードは次のJavaScriptコードにコンパイルされます。
"use strict";
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
this.instantiatedAt = new Date();
}
}
これは、元の例がコンパイルされるのと同じJavaScriptコードです。
TypeScriptクラスのプロパティの設定を試したので、クラス継承を使用してクラスを新しいクラスに拡張することに進むことができます。
TypeScriptでのクラス継承
TypeScriptは、JavaScriptのクラス継承の全機能を提供し、インターフェイスと抽象クラスの2つの主な追加機能があります。 インターフェイスは、より複雑なデータの型チェックを提供するように、クラスまたはオブジェクトの形状を記述および適用する構造です。 クラスにインターフェースを実装して、それが特定のパブリックシェイプを持っていることを確認できます。 抽象クラスは、他のクラスの基礎として機能するクラスですが、それ自体をインスタンス化することはできません。 これらは両方とも、クラス継承を介して実装されます。
このセクションでは、インターフェイスと抽象クラスを使用してクラスの型チェックを構築および作成する方法の例をいくつか紹介します。
インターフェイスの実装
インターフェイスは、そのインターフェイスのすべての実装が持つ必要のある一連の動作を指定するのに役立ちます。 インターフェイスは、interface
キーワード、インターフェイスの名前、インターフェイス本体の順に使用して作成されます。 例として、プログラムの実行方法に関する重要なデータをログに記録するために使用できるLogger
インターフェイスを作成します。
interface Logger {}
次に、インターフェースに4つのメソッドを追加します。
interface Logger {
debug(message: string, metadata?: Record<string, unknown>): void;
info(message: string, metadata?: Record<string, unknown>): void;
warning(message: string, metadata?: Record<string, unknown>): void;
error(message: string, metadata?: Record<string, unknown>): void;
}
このコードブロックに示されているように、インターフェイスでメソッドを作成するときは、実装を追加せず、タイプ情報のみを追加します。 この場合、debug
、info
、warning
、およびerror
の4つの方法があります。 それらはすべて同じ型署名を共有します。2つのパラメーター、タイプstring
のmessage
と、タイプRecord<string, unknown>
のオプションのmetadata
パラメーターを受け取ります。 それらはすべてvoid
タイプを返します。
このインターフェースを実装するすべてのクラスには、これらの各メソッドに対応するパラメーターと戻り値のタイプが必要です。 ConsoleLogger
という名前のクラスにインターフェイスを実装します。このクラスは、console
メソッドを使用してすべてのメッセージをログに記録します。
class ConsoleLogger implements Logger {
debug(message: string, metadata?: Record<string, unknown>) {
console.info(`[DEBUG] ${message}`, metadata);
}
info(message: string, metadata?: Record<string, unknown>) {
console.info(message, metadata);
}
warning(message: string, metadata?: Record<string, unknown>) {
console.warn(message, metadata);
}
error(message: string, metadata?: Record<string, unknown>) {
console.error(message, metadata);
}
}
インターフェイスを作成するときに、implements
という新しいキーワードを使用して、クラスが実装するインターフェイスのリストを指定していることに注意してください。 implements
キーワードの後に、インターフェイスIDのコンマ区切りリストとして追加することにより、複数のインターフェイスを実装できます。 たとえば、Clearable
という別のインターフェイスがある場合:
interface Clearable {
clear(): void;
}
次の強調表示されたコードを追加することで、ConsoleLogger
クラスに実装できます。
class ConsoleLogger implements Logger, Clearable {
clear() {
console.clear();
}
debug(message: string, metadata?: Record<string, unknown>) {
console.info(`[DEBUG] ${message}`, metadata);
}
info(message: string, metadata?: Record<string, unknown>) {
console.info(message, metadata);
}
warning(message: string, metadata?: Record<string, unknown>) {
console.warn(message, metadata);
}
error(message: string, metadata?: Record<string, unknown>) {
console.error(message, metadata);
}
}
クラスが新しいインターフェイスに準拠していることを確認するために、clear
メソッドも追加する必要があることに注意してください。
Logger
インターフェイスのdebug
メソッドのように、いずれかのインターフェイスに必要なメンバーの1つに実装を提供しなかった場合、TypeScriptコンパイラはエラー
OutputClass 'ConsoleLogger' incorrectly implements interface 'Logger'.
Property 'debug' is missing in type 'ConsoleLogger' but required in type 'Logger'. (2420)
TypeScriptコンパイラは、実装が実装しているインターフェイスで期待されるものと一致しなかった場合にもエラーを表示します。 たとえば、debug
メソッドのmessage
パラメータのタイプをstring
からnumber
に変更すると、エラー2416
が発生します。 ]:
OutputProperty 'debug' in type 'ConsoleLogger' is not assignable to the same property in base type 'Logger'.
Type '(message: number, metadata?: Record<string, unknown> | undefined) => void' is not assignable to type '(message: string, metadata: Record<string, unknown>) => void'.
Types of parameters 'message' and 'message' are incompatible.
Type 'string' is not assignable to type 'number'. (2416)
抽象クラスに基づいて構築する
抽象クラスは通常のクラスに似ていますが、2つの大きな違いがあります。直接インスタンス化できないことと、抽象メンバーが含まれている可能性があることです。 抽象メンバーは、継承クラスで実装する必要のあるメンバーです。 抽象クラス自体には実装がありません。 これは、基本抽象クラスにいくつかの共通機能を持ち、継承クラスにさらに具体的な実装を含めることができるので便利です。 クラスを抽象としてマークすると、このクラスには、継承するクラスで実装する必要のある機能が不足していると言えます。
抽象クラスを作成するには、強調表示されたコードのように、class
キーワードの前にabstract
キーワードを追加します。
abstract class AbstractClassName {
}
次に、抽象クラスにメンバーを作成できます。実装がある場合とない場合があります。 実装されていないものはabstract
としてマークされ、抽象クラスから拡張されたクラスに実装する必要があります。
たとえば、 Node.js 環境で作業していて、独自のStream実装を作成しているとします。 そのために、2つの抽象メソッドread
とwrite
を持つStream
という抽象クラスを作成します。
declare class Buffer {
from(array: any[]): Buffer;
copy(target: Buffer, offset?: number): void;
}
abstract class Stream {
abstract read(count: number): Buffer;
abstract write(data: Buffer): void;
}
ここでのBufferオブジェクトは、バイナリデータを格納するために使用されるNode.jsで利用可能なクラスです。 上部のdeclare class Buffer
ステートメントを使用すると、TypeScriptPlaygroundのようなNode.js型宣言なしでTypeScript環境でコードをコンパイルできます。
この例では、read
メソッドは内部データ構造からバイトをカウントし、Buffer
オブジェクトを返し、write
はBuffer
インスタンスのすべてのコンテンツを書き込みますストリームに。 これらのメソッドは両方とも抽象的であり、Stream
から拡張されたクラスでのみ実装できます。
次に、実装がある追加のメソッドを作成できます。 このようにして、Stream
抽象クラスから拡張するクラスは、これらのメソッドを自動的に受け取ります。 そのような例の1つは、copy
メソッドです。
declare class Buffer {
from(array: any[]): Buffer;
copy(target: Buffer, offset?: number): void;
}
abstract class Stream {
abstract read(count: number): Buffer;
abstract write(data: Buffer): void;
copy(count: number, targetBuffer: Buffer, targetBufferOffset: number) {
const data = this.read(count);
data.copy(targetBuffer, targetBufferOffset);
}
}
このcopy
メソッドは、targetBufferOffset
から開始して、ストリームからtargetBuffer
にバイトを読み取った結果をコピーします。
次に、FileStream
クラスのように、Stream
抽象クラスの実装を作成すると、FileStream
クラス:
declare class Buffer {
from(array: any[]): Buffer;
copy(target: Buffer, offset?: number): void;
}
abstract class Stream {
abstract read(count: number): Buffer;
abstract write(data: Buffer): void;
copy(count: number, targetBuffer: Buffer, targetBufferOffset: number) {
const data = this.read(count);
data.copy(targetBuffer, targetBufferOffset);
}
}
class FileStream extends Stream {
read(count: number): Buffer {
// implementation here
return new Buffer();
}
write(data: Buffer) {
// implementation here
}
}
const fileStream = new FileStream();
この例では、fileStream
インスタンスで自動的にcopy
メソッドを使用できます。 FileStream
クラスは、Stream
抽象クラスに準拠するために、read
およびwrite
メソッドも明示的に実装する必要がありました。
FileStream
クラスにwrite
実装を追加しないなど、拡張元の抽象クラスの抽象メンバーの1つを実装するのを忘れた場合、TypeScriptコンパイラはエラー2515
:
OutputNon-abstract class 'FileStream' does not implement inherited abstract member 'write' from class 'Stream'. (2515)
write
メソッドの最初のパラメーターのタイプをstring
タイプに変更するなど、メンバーのいずれかを誤って実装した場合にも、TypeScriptコンパイラーはエラーを表示します。 X212X]:
OutputProperty 'write' in type 'FileStream' is not assignable to the same property in base type 'Stream'.
Type '(data: string) => void' is not assignable to type '(data: Buffer) => void'.
Types of parameters 'data' and 'data' are incompatible.
Type 'Buffer' is not assignable to type 'string'. (2416)
抽象クラスとインターフェースを使用すると、クラスのより複雑な型チェックをまとめて、基本クラスから拡張されたクラスが正しい機能を継承していることを確認できます。 次に、TypeScriptでメソッドとプロパティの可視性がどのように機能するかの例を実行します。
クラスメンバーの可視性
TypeScriptは、クラスのメンバーの可視性を指定できるようにすることで、使用可能なJavaScriptクラス構文を拡張します。 この場合、 visibility は、インスタンス化されたクラスの外部のコードがクラス内のメンバーとどのように相互作用できるかを示します。
TypeScriptのクラスメンバーには、public
、protected
、およびprivate
の3つの可視性修飾子があります。 public
メンバーは、クラスインスタンスの外部からアクセスできますが、private
メンバーはアクセスできません。 protected
は、2つの中間点を占め、クラスのインスタンスまたはそのクラスに基づくサブクラスからメンバーにアクセスできます。
このセクションでは、使用可能な可視性修飾子を調べて、それらの意味を学習します。
public
これは、TypeScriptのクラスメンバーのデフォルトの可視性です。 クラスメンバーに可視性修飾子を追加しない場合は、public
に設定するのと同じです。 パブリッククラスのメンバーは、制限なしでどこからでもアクセスできます。
これを説明するために、前のPerson
クラスに戻ります。
class Person {
public instantiatedAt = new Date();
constructor(
name: string,
age: number
) {}
}
このチュートリアルでは、2つのプロパティname
とage
がデフォルトでpublic
の可視性を持っていることを説明しました。 型の可視性を明示的に宣言するには、プロパティの前にpublic
キーワードを追加し、getBirthYear
という新しいpublic
メソッドをクラスに追加します。これにより、Person
インスタンス:
class Person {
constructor(
public name: string,
public age: number
) {}
public getBirthYear() {
return new Date().getFullYear() - this.age;
}
}
次に、クラスインスタンスの外部のグローバルスペースでプロパティとメソッドを使用できます。
class Person {
constructor(
public name: string,
public age: number
) {}
public getBirthYear() {
return new Date().getFullYear() - this.age;
}
}
const jon = new Person("Jon", 35);
console.log(jon.name);
console.log(jon.age);
console.log(jon.getBirthYear());
このコードは、コンソールに次のように出力します。
OutputJon
35
1986
クラスのすべてのメンバーにアクセスできることに注意してください。
protected
protected
の可視性を持つクラスメンバーは、そのクラス内またはそのクラスのサブクラスで宣言されているクラス内でのみ使用できます。
次のEmployee
クラスとそれに基づくFinanceEmployee
クラスを見てください。
class Employee {
constructor(
protected identifier: string
) {}
}
class FinanceEmployee extends Employee {
getFinanceIdentifier() {
return `fin-${this.identifier}`;
}
}
強調表示されたコードは、protected
の可視性で宣言されたidentifier
プロパティを示しています。 this.identifier
コードは、FinanceEmployee
サブクラスからこのプロパティにアクセスしようとします。 このコードは、TypeScriptでエラーなしで実行されます。
次の例のように、クラス自体またはサブクラス内にない場所からそのメソッドを使用しようとした場合:
class Employee {
constructor(
protected identifier: string
) {}
}
class FinanceEmployee extends Employee {
getFinanceIdentifier() {
return `fin-${this.identifier}`;
}
}
const financeEmployee = new FinanceEmployee('abc-12345');
financeEmployee.identifier;
TypeScriptコンパイラはエラー2445
を出します:
OutputProperty 'identifier' is protected and only accessible within class 'Employee' and its subclasses. (2445)
これは、新しいfinanceEmployee
インスタンスのidentifier
プロパティをグローバルスペースから取得できないためです。 代わりに、内部メソッドgetFinanceIdentifier
を使用して、identifier
プロパティを含む文字列を返す必要があります。
class Employee {
constructor(
protected identifier: string
) {}
}
class FinanceEmployee extends Employee {
getFinanceIdentifier() {
return `fin-${this.identifier}`;
}
}
const financeEmployee = new FinanceEmployee('abc-12345');
console.log(financeEmployee.getFinanceIdentifier())
これにより、以下がコンソールに記録されます。
Outputfin-abc-12345
private
プライベートメンバーは、それらを宣言するクラス内でのみアクセスできます。 これは、サブクラスでさえアクセスできないことを意味します。
前の例を使用して、Employee
クラスのidentifier
プロパティをprivate
プロパティに変換します。
class Employee {
constructor(
private identifier: string
) {}
}
class FinanceEmployee extends Employee {
getFinanceIdentifier() {
return `fin-${this.identifier}`;
}
}
このコードにより、TypeScriptコンパイラはエラー2341
を表示します。
OutputProperty 'identifier' is private and only accessible within class 'Employee'. (2341)
これは、FinanceEmployee
サブクラスのプロパティidentifier
にアクセスしているために発生しますが、identifier
プロパティがEmployee
クラスで宣言されているため、これは許可されていません。可視性はprivate
に設定されています。
TypeScriptは生のJavaScriptにコンパイルされており、それ自体にはクラスのメンバーの可視性を指定する方法がないことに注意してください。 そのため、TypeScriptには、実行時のそのような使用に対する保護はありません。 これは、コンパイル中にのみTypeScriptコンパイラによって実行される安全性チェックです。
可視性修飾子を試したので、TypeScriptクラスのメソッドとして矢印関数に進むことができます。
矢印関数としてのクラスメソッド
JavaScriptでは、関数のコンテキストを表す this 値は、関数の呼び出し方法に応じて変わる可能性があります。 この変動性は、複雑なコードで混乱を招く場合があります。 TypeScriptを使用する場合、クラスメソッドを作成するときに特別な構文を使用して、this
がクラスインスタンス以外のものにバインドされないようにすることができます。 このセクションでは、この構文を試してみます。
Employee
クラスを使用して、従業員IDの取得にのみ使用される新しいメソッドを導入します。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier() {
return this.identifier;
}
}
これは、メソッドを直接呼び出す場合に非常にうまく機能します。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier() {
return this.identifier;
}
}
const employee = new Employee("abc-123");
console.log(employee.getIdentifier());
これにより、コンソールの出力に次のように出力されます。
Outputabc-123
ただし、次のコードのように、後で呼び出すためにgetIdentifier
インスタンスメソッドをどこかに保存した場合。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier() {
return this.identifier;
}
}
const employee = new Employee("abc-123");
const obj = {
getId: employee.getIdentifier
}
console.log(obj.getId());
値にアクセスできなくなります。
Outputundefined
これは、obj.getId()
を呼び出すと、employee.getIdentifier
内のthis
がEmployee
ではなくobj
オブジェクトにバインドされるためです。実例。
これを回避するには、getIdentifier
を矢印関数に変更します。 次のコードで強調表示されている変更を確認してください。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier = () => {
return this.identifier;
}
}
...
以前と同じようにobj.getId()
を呼び出そうとすると、コンソールに次のように正しく表示されます。
Outputabc-123
これは、TypeScriptで矢印関数をクラスメソッドの直接値として使用する方法を示しています。 次のセクションでは、TypeScriptのタイプチェックを使用してクラスを適用する方法を学習します。
タイプとしてのクラスの使用
これまでのところ、このチュートリアルでは、クラスを作成して直接使用する方法について説明してきました。 このセクションでは、TypeScriptを使用するときに、クラスを型として使用します。
クラスはTypeScriptではタイプと値の両方であるため、両方の方法で使用できます。 クラスを型として使用するには、TypeScriptが型を予期する任意の場所でクラス名を使用します。 たとえば、前に作成したEmployee
クラスがあるとします。
class Employee {
constructor(
public identifier: string
) {}
}
従業員の識別子を出力する関数を作成したいとします。 次のような関数を作成できます。
class Employee {
constructor(
public identifier: string
) {}
}
function printEmployeeIdentifier(employee: Employee) {
console.log(employee.identifier);
}
employee
パラメーターをEmployee
タイプに設定していることに注意してください。これは、クラスの正確な名前です。
TypeScriptのクラスは、他のタイプがTypeScriptで比較されるのと同じように、他のクラスを含む他のタイプと構造的に比較されます。 つまり、両方が同じ形状の2つの異なるクラス(つまり、同じ可視性を持つ同じメンバーのセット)がある場合、どちらか一方のみが必要な場所で両方を交換して使用できます。
これを説明するために、アプリケーションにWarehouse
という別のクラスがあると想像してください。
class Warehouse {
constructor(
public identifier: string
) {}
}
Employee
と同じ形です。 そのインスタンスをprintEmployeeIdentifier
に渡そうとした場合:
class Employee {
constructor(
public identifier: string
) {}
}
class Warehouse {
constructor(
public identifier: string
) {}
}
function printEmployeeIdentifier(employee: Employee) {
console.log(employee.identifier);
}
const warehouse = new Warehouse("abc");
printEmployeeIdentifier(warehouse);
TypeScriptコンパイラは文句を言いません。 クラスのインスタンスの代わりに、通常のオブジェクトだけを使用することもできます。 これにより、TypeScriptを使い始めたばかりのプログラマーが予期しない動作が発生する可能性があるため、これらのシナリオに注意を払うことが重要です。
クラスをタイプとして使用する基本が邪魔にならないので、形状だけでなく、特定のクラスをチェックする方法を学ぶことができます。
this
のタイプ
クラス自体のいくつかのメソッド内で現在のクラスのタイプを参照する必要がある場合があります。 このセクションでは、this
を使用してこれを実現する方法を説明します。
Employee
クラスにisSameEmployeeAs
という新しいメソッドを追加する必要があるとします。このメソッドは、別の従業員インスタンスが現在の従業員と同じ従業員を参照しているかどうかを確認します。 これを行う1つの方法は、次のようになります。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier() {
return this.identifier;
}
isSameEmployeeAs(employee: Employee) {
return this.identifier === employee.identifier;
}
}
このテストは、Employee
から派生したすべてのクラスのidentifier
プロパティを比較するために機能します。 ただし、Employee
の特定のサブクラスをまったく比較したくないシナリオを想像してみてください。 この場合、比較のブール値を受け取る代わりに、2つの異なるサブクラスが比較されたときにTypeScriptがエラーを報告するようにします。
たとえば、財務部門とマーケティング部門の従業員用に2つの新しいサブクラスを作成します。
...
class FinanceEmployee extends Employee {
specialFieldToFinanceEmployee = '';
}
class MarketingEmployee extends Employee {
specialFieldToMarketingEmployee = '';
}
const finance = new FinanceEmployee("fin-123");
const marketing = new MarketingEmployee("mkt-123");
marketing.isSameEmployeeAs(finance);
ここでは、Employee
基本クラスからFinanceEmployee
とMarketingEmployee
の2つのクラスを派生させます。 それぞれに異なる新しいフィールドがあります。 次に、それぞれのインスタンスを1つ作成し、marketing
の従業員がfinance
の従業員と同じであるかどうかを確認します。 このシナリオでは、サブクラスはまったく比較されないため、TypeScriptはエラーを報告する必要があります。 isSameEmployeeAs
メソッドのemployee
パラメーターの型としてEmployee
を使用したため、これは発生しません。Employee
から派生したすべてのクラスが型を渡します。 -チェック中。
このコードを改善するために、クラス内で使用可能な特別なタイプであるthis
タイプを使用できます。 このタイプは、現在のクラスのタイプに動的に設定されます。 このように、このメソッドが派生クラスで呼び出されると、this
が派生クラスのタイプに設定されます。
代わりにthis
を使用するようにコードを変更してください。
class Employee {
constructor(
protected identifier: string
) {}
getIdentifier() {
return this.identifier;
}
isSameEmployeeAs(employee: this) {
return this.identifier === employee.identifier;
}
}
class FinanceEmployee extends Employee {
specialFieldToFinanceEmployee = '';
}
class MarketingEmployee extends Employee {
specialFieldToMarketingEmployee = '';
}
const finance = new FinanceEmployee("fin-123");
const marketing = new MarketingEmployee("mkt-123");
marketing.isSameEmployeeAs(finance);
このコードをコンパイルすると、TypeScriptコンパイラはエラー2345
を表示するようになりました。
OutputArgument of type 'FinanceEmployee' is not assignable to parameter of type 'MarketingEmployee'.
Property 'specialFieldToMarketingEmployee' is missing in type 'FinanceEmployee' but required in type 'MarketingEmployee'. (2345)
this
キーワードを使用すると、さまざまなクラスコンテキストで入力を動的に変更できます。 次に、クラスのインスタンスではなく、クラス自体を渡すために入力を使用します。
コンストラクト署名の使用
プログラマーは、インスタンスではなく、クラスを直接受け取る関数を作成する必要がある場合があります。 そのためには、構成シグネチャを持つ特別なタイプを使用する必要があります。 このセクションでは、そのようなタイプを作成する方法について説明します。
クラス自体を渡す必要がある特定のシナリオの1つは、クラスファクトリ、または引数として渡されるクラスの新しいインスタンスを生成する関数です。 Employee
に基づくクラスを取得し、増分された識別子を使用して新しいインスタンスを作成し、その識別子をコンソールに出力する関数を作成するとします。 次のようにこれを作成しようとするかもしれません:
class Employee {
constructor(
public identifier: string
) {}
}
let identifier = 0;
function createEmployee(ctor: Employee) {
const employee = new ctor(`test-${identifier++}`);
console.log(employee.identifier);
}
このスニペットでは、Employee
クラスを作成し、identifier
を初期化し、Employee
。 しかし、このコードをコンパイルしようとすると、TypeScriptコンパイラはエラー2351
を出します。
OutputThis expression is not constructable.
Type 'Employee' has no construct signatures. (2351)
これは、クラスの名前をctor
の型として使用する場合、その型はクラスのインスタンスに対してのみ有効であるために発生します。 クラスコンストラクター自体の型を取得するには、typeof ClassName
を使用する必要があります。 次の強調表示されたコードを変更して確認してください。
class Employee {
constructor(
public identifier: string
) {}
}
let identifier = 0;
function createEmployee(ctor: typeof Employee) {
const employee = new ctor(`test-${identifier++}`);
console.log(employee.identifier);
}
これで、コードは正常にコンパイルされます。 ただし、まだ保留中の問題があります。クラスファクトリは基本クラスから構築された新しいクラスのインスタンスを構築するため、abstract
クラスを使用するとワークフローが改善される可能性があります。 ただし、これは最初は機能しません。
これを試すには、Employee
クラスをabstract
クラスに変換します。
abstract class Employee {
constructor(
public identifier: string
) {}
}
let identifier = 0;
function createEmployee(ctor: typeof Employee) {
const employee = new ctor(`test-${identifier++}`);
console.log(employee.identifier);
}
TypeScriptコンパイラはエラー2511
を出すようになりました:
OutputCannot create an instance of an abstract class. (2511)
このエラーは、abstract
であるため、Employee
クラスからインスタンスを作成できないことを示しています。 ただし、このような関数を使用して、Employee
抽象クラスから拡張するさまざまな種類の従業員を次のように作成することもできます。
abstract class Employee {
constructor(
public identifier: string
) {}
}
class FinanceEmployee extends Employee {}
class MarketingEmployee extends Employee {}
let identifier = 0;
function createEmployee(ctor: typeof Employee) {
const employee = new ctor(`test-${identifier++}`);
console.log(employee.identifier);
}
createEmployee(FinanceEmployee);
createEmployee(MarketingEmployee);
このシナリオでコードを機能させるには、コンストラクターシグネチャを持つ型を使用する必要があります。 これを行うには、new
キーワードを使用し、その後に矢印関数と同様の構文を使用します。ここで、パラメーターリストにはコンストラクターが期待するパラメーターが含まれ、戻り型はこのコンストラクターが返すクラスインスタンスです。
次のコードで強調表示されているのは、コンストラクター署名付きの型をcreateEmployee
関数に導入する変更です。
abstract class Employee {
constructor(
public identifier: string
) {}
}
class FinanceEmployee extends Employee {}
class MarketingEmployee extends Employee {}
let identifier = 0;
function createEmployee(ctor: new (identifier: string) => Employee) {
const employee = new ctor(`test-${identifier++}`);
console.log(employee.identifier);
}
createEmployee(FinanceEmployee);
createEmployee(MarketingEmployee);
TypeScriptコンパイラがコードを正しくコンパイルするようになりました。
結論
TypeScriptのクラスは、型システム、矢印関数メソッドなどの追加の構文、およびメンバーの可視性や抽象クラスなどの完全に新しい機能にアクセスできるため、JavaScriptよりもさらに強力です。 これにより、タイプセーフで信頼性が高く、アプリケーションのビジネスモデルをより適切に表すコードを提供できます。
TypeScriptのその他のチュートリアルについては、TypeScriptシリーズのコーディング方法ページをご覧ください。