TypeScriptで使うクラスの書き方を自分用のメモとしてまとめてみました!
参考になれば幸いです。
ちなみにクラスやTypeScriptの基本的な型定義は下記をどうぞ↓
【解説】JavaScriptのクラスの使い方【フィールド/static/extends/super】 【解説】TypeScriptの型定義をまとめてみた【初心者向け】目次
【解説】TypeScriptで使うクラスの型【まとめてみた】
TypeScriptでのクラスの型について、下記の順に解説していきます。
・クラスの基本的な使い方
・クラスのthisの注意点
・クラスを型に使う
・クラスに型を指定する
・「public修飾子」「private修飾子」
・クラスの初期化処理を省略する
・「readonly修飾子」
・「protected修飾子」
・Abstractクラス(抽象クラス)
・シングルトンパターン
1つずつ見ていきましょう。
まず基本的なクラスを使った例となるコードがこちら↓
class Fruit {
name: string;
constructor(fruitName: string) {
this.name = fruitName;
}
nameFunc() {
console.log(`この果物の名前は${this.name}です。`);
}
}
const orange = new Fruit('みかん');
orange.nameFunc(); // この果物の名前はみかんです。
Fruitというクラスを作り、果物の名前をコンソールログで出力するメソッドを入れました。
これでインスタンス化して引数に果物名を入れ、メソッドを呼び出せば名前が出力できます。
このコードを基本として、これからクラスで使える型を解説していきます。
クラスでthisを使うときの注意点があります。
それが新たに作ったオブジェクトの中に、クラスのメソッドを入れると「this ~~」が「undefined」になることです。
class Fruit {
name: string;
constructor(fruitName: string) {
this.name = fruitName;
}
nameFunc() {
console.log(`この果物の名前は${this.name}です。`);
}
}
const orange = new Fruit('みかん');
const otherFruit = {
otherFruitName :orange.nameFunc,
}
otherFruit.otherFruitName(); // この果物の名前はundefinedです。
thisは実行部分に影響されるので、上記だと「otherFruit」というオブジェクト内にnameがないので未定義となります。
未定義となるだけで、TypeScriptがエラーで表示はしてくれません。。
TypeScriptだとそのthisがなに使ってるかまで、見てくれない。。
そこでクラスのメソッドの第一引数に、偽の「this」という名前をつけます。
nameFunc(this: {name: string}) {
~~
}
class Fruit {
name: string;
constructor(fruitName: string) {
this.name = fruitName;
}
nameFunc(this: {name: string}) {
console.log(`この果物の名前は${this.name}です。`);
}
}
const orange = new Fruit('みかん');
const otherFruit = {
name: '新しいみかん',
otherFruitName :orange.nameFunc,
}
otherFruit.otherFruitName(); // この果物の名前はみかんです。
これでnameプロパティがないとちゃんとエラーを表示してくれます↓
実はTypeScriptではクラスを作るときに、同時にそのクラスの型も同じ名前で作成してくれます。
そのためthisに対して、そのクラスの型名を記述してもうまく動きます。
class Fruit {
name: string;
constructor(fruitName: string) {
this.name = fruitName;
}
nameFunc(this: Fruit) {
console.log(`この果物の名前は${this.name}です。`);
}
}
const orange = new Fruit('みかん');
const otherFruit = {
name: '新しいみかん', // ないとエラーが出る
nameFunc :orange.nameFunc, // ないとエラーが出る
}
otherFruit.nameFunc();
これで「otherFruit」のなかに、nameプロパティ・nameFuncメソッドがないとエラーがでるようになります。
TypeScriptではクラスに型を指定することが可能です。
「interface」もしくは「typeエイリアス」で指定した型を、「implements」を使ってクラスに指定することができます。
interface FruitInfo {
name: string;
nameFunc(): void;
} // ちなみにtypeエイリアスでもOK
class Fruit implements FruitInfo {
name: string;
constructor(fruitName: string) {
this.name = fruitName;
}
nameFunc() {
console.log(`この果物の名前は${this.name}です。`);
}
}
const orange = new Fruit('みかん');
orange.nameFunc(); // この果物の名前はみかんです。
これで「Fruit」クラスには、「FruitInfo」で定義したプロパティやメソッドが必要になります。
ただしそれよりも多いプロパティやメソッドを、「Fruit」クラスで定義するぶんにはエラーがでません。
【解説】TypeScriptのインターフェースの使い方【interface】プロパティやメソッドの前に下記修飾子をつけることで、アクセスできるかを指定できます。
- 「public修飾子」⇒クラスの外でも中でもアクセス可能。デフォルト。
- 「private修飾子」⇒クラスの中でのみアクセス可能。
class Fruit {
public name: string;
private num: number;
constructor(fruitName: string, fruitNum: number) {
this.name = fruitName;
this.num = fruitNum;
}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit('みかん', 3);
orange.name = 'オレンジ' // これはOK
orange.num = 5; // これはエラーがでる
ちなみに「private修飾子」はインスタンス化するときには使用可能です。
(コンストラクタ内で初期化するので)
constructorのパラメータの先頭に「pbulic」や「private」の修飾子をつけることで、初期化処理を省略することができます。
class Fruit {
constructor(public name: string, private num: number) {
}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit('みかん', 3);
orange.nameFunc(); // この果物の名前はみかんで数は3です。
「readonly修飾子」をつけることで、読み取り専用にすることができます。
- 読み取り専用
- 書き込みはできない
- フィールドでも使用可能
- constructor内の初期化では書き込み可能
- 「public修飾子」「private修飾子」も付ける場合は、その後ろに記述
class Fruit {
constructor(public readonly name: string, private readonly num: number) {
this.name = 'りんご';
this.num = 5;
}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit('みかん', 3);
orange.name = 'ぶどう'; // これはエラーが出ます。
orange.nameFunc(); // この果物の名前はみかんで数は3です。
クラスには「protected修飾子」という、継承先まではアクセス可能な「private」を少しゆるくしたものが使えます。
- 「private修飾子」⇒クラスの継承先もアクセスできない
- 「protected修飾子」⇒継承先まではアクセス可能。外からはアクセス不可能。
例として「private修飾子」を用いたクラスを継承するコードを書いてみました。
class Fruit {
constructor(public name: string, private num: number) {
}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit('みかん', 3);
class Food extends Fruit {
constructor(name: string, num: number, public volume: number ) {
super(name, num);
}
nameFunc() {
console.log(`この食べ物の名前は${this.name}で数は${this.num}です。量は${this.volume}です。`);
}
}
const drink = new Food('りんご', 5, 10);
このままだと「Fruit」クラスのnumがprivateのため、「Food」クラスにてnumにアクセスすることができません。
エラーがでてしまいます↓
そこで使えるのが「protected修飾子」です。
継承先まではアクセスすることができるため、下記だとエラーがでません。
class Fruit {
constructor(public name: string, protected num: number) {
}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit('みかん', 3);
class Food extends Fruit {
constructor(name: string, num: number, public volume: number ) {
super(name, num);
}
nameFunc() {
console.log(`この食べ物の名前は${this.name}で数は${this.num}です。量は${this.volume}です。`);
}
}
const apple = new Food('りんご', 5, 10);
Abstractとは「設計ルールを持つ、インスタンス化できない親クラス」です。
ようは継承のために使われるクラスのこと。
継承先で使われるであろう処理を親クラスで書いておいて、子で使う感じです。
子クラスに「これだけは必ず実装してね」と約束してるクラスとも言えるね
例として書いたコードがこちら
親クラス「Fruit」で記述したメソッドを、「Food」にて実行しております。
↓
abstract class Fruit {
constructor(public name: string, protected num: number) {}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
this.getDescription();
}
abstract getDescription(): void;
}
class Food extends Fruit {
constructor(name: string, num: number, private extra: number) {
super(name, num);
}
getDescription() {
console.log(`${this.name}は食べ物です。数は${this.num}で、追加で${this.extra}個あります。`);
}
}
const apple = new Food('りんご', 5, 10);
apple.nameFunc();
// この果物の名前はりんごで数は5です。
// りんごは食べ物です。数は5で、追加で10個あります。
- 1行目:「abstract」を使うには、クラスの先頭に「abstract」をつける
- 5行目:子で使う処理を、親で記述
- 7行目:継承先で必ず使う処理に対して、先頭に「abstract」を記述
- 7行目:暗黙的なanyではいけない(今回は: voidを記述)
- 14 ~ 16行目:親で書いていた処理名を、子で実装
- この親クラスはインスタンス化することはできない
constructorには「private修飾子」をつけることが可能です。
class Fruit {
private constructor(public name: string, protected num: number) {}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
}
const orange = new Fruit(); // エラーがでます。
これをするとインスタンス化することができません。
これはシングルトンパターンという「クラスのインスタンスがただ1つしか存在しない」ものを用いるときに使えるものです。
いわゆるデザインパターンだね
ただインスタンス化できないとなると、どう使うのでしょうか。
staticを使えばクラスの中でインスタンス化が可能です↓
class Fruit {
private constructor(public name: string, protected num: number) {}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
static getfruitInstance() {
const fruitInstance = new Fruit('みかん', 5);
return fruitInstance
}
}
これでインスタンス化ができました。
ただこのままでは「fruitInstance」「fruitInstance02」「fruitInstance03」みたいに変数をたくさん作れば作るほど、インスタンス化できちゃいますよね。。
ではどうするのか。。
そこで「唯一のインスタンスを保持する静的プロパティ」を作ります。
class Fruit {
private static instance: Fruit;
private constructor(public name: string, protected num: number) {}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
static getfruitInstance() {
const fruitInstance = new Fruit('みかん', 5);
return fruitInstance
}
}
こうすると「getfruitInstanceメソッド」から、staticなinstanceにアクセスすることができます。
あとはインスタンス化がされてるかどうかを、if文で判定すれば対応可能です。
インスタンス化されてればインスタンスを返して、されていなければインスタンス化するということですね。
class Fruit {
private static instance: Fruit;
private constructor(public name: string, protected num: number) {}
nameFunc() {
console.log(`この果物の名前は${this.name}で数は${this.num}です。`);
}
static getfruitInstance() {
if (!Fruit.instance) {
Fruit.instance = new Fruit('みかん', 5);
}
return Fruit.instance
}
}
これでインスタンスが1つしか作られないクラスができました!
【解説】TypeScriptで使うクラスの型【まとめてみた】:まとめ
- TypeScriptのクラスでthisを使うときは注意
- TypeScriptではクラス作成時にそのクラスの型も作る
- 「implements」を使えばクラスに型を指定できる
- 「public修飾子」⇒クラスの外でも中でもアクセス可能
- 「private修飾子」⇒クラスの中でのみアクセス可能
- 「readonly修飾子」⇒読み取り専用
- 「protected修飾子」⇒クラスの継承先はアクセス可能。外は不可
- 「Abstract」⇒継承先で使われる処理を親クラスで書き、子で使う
- 「シングルトンパターン」⇒クラスのインスタンスがただ1つしか存在しないデザインパターン
TypeScriptでクラスを使う時はためしてみてね!
ちなみにクラスやTypeScriptの基本的な型定義は下記をどうぞ↓
【解説】JavaScriptのクラスの使い方【フィールド/static/extends/super】 【解説】TypeScriptの型定義をまとめてみた【初心者向け】