• 最終更新日:

TypeScriptでClassを使う

この記事の目次

基本的な使い方は以下の通り。

class名は最初を大文字にすることでclassだと判別できる。newでクラス名を指定することでオブジェクトを作成できる。

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
}
new Animals('ライオン');

クラスにメソッドを追加する場合

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  intro() {
    console.log(`この動物は${this.name}です`);
  }
}
new Animals('ライオン');

実際に実行する場合は以下。

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  intro() {
    console.log(`この動物は${this.name}です`);
  }
}
const lion = new Animals('ライオン');
lion.intro();

classを使ったときにthisの注意点

オブジェクトの中で、クラスの中にあるメソッドを値として設定した場合、this.nameがundefineになる。

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  intro() {
    console.log(`この動物は${this.name}です`);
  }
}
const lion = new Animals('ライオン');
//lion.intro();

const anotherLion = {
  anotherIntro: lion.intro,
};
anotherLion.anotherIntro(); //この動物はundefineです と表示される

thisは呼び出した場所によって値が変化するため、TypeScriptではそこまで面倒見切れない。そこでthisが何か伝える方法がある。

introメソッドにある第一引数にthisをとる。これはダミーのthisで第一引数にしかとれない。これによって、anotherLion.anotherIntro()を呼び出したときにnameプロパティがないとエラーで表示される。

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  intro(this:{ name:string }) {
    console.log(`この動物は${this.name}です`);
  }
}
const lion = new Animals('ライオン');

const anotherLion = {
  //name:"シマウマ", //本来はこのプロパティが必要
  anotherIntro: lion.intro,
};
anotherLion.anotherIntro(); //anotherLionにはプロパティnameがないよ、というエラー

classを型のように使う

classはnewでインスタンス化されたときに、そのclassの型も一緒に作成する。以下はthisに対してAnimalsの型を指定してます。

この場合thisにはAnimalsという型が指定されているので、nameというプロパティとintorというメソッドを持っていないとエラーになる。

class Animals {
  name: string;
  constructor(_name: string) {
    this.name = _name;
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です`);
  }
}
const lion = new Animals('ライオン');

const anotherLion = {
  name:"シマウマ", //これがないとエラーになる
  intro:lion.intro //これがないとエラーになる
};
anotherLion.intro();

private修飾子とpublic修飾子とは?

private修飾子はclassの中ではアクセスできるが、classの外ではアクセスできないようにするもの。publicはデフォルトの指定で、classの中でも外でも使うことができる。

class Animals {
  name: string;
  age:number;
  constructor(_name: string,_age:number) {
    this.name = _name;
    this.age = _age;
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン',12);

//この状態だとageの値を外からでも変更できてしまう
lion.age = 50; //これができちゃう

外からアクセスさせないためには、private修飾子をつける必要がある。
プロパティでもメソッドでも使える。

class Animals {
  private name: string;
  private age: number;
  constructor(_name: string, _age: number) {
    this.name = _name;
    this.age = _age;
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

//lion.age = 50; これができなくなる
//console.log(lion.age); //外から参照もできなくなる

classで初期化の処理を省略する書き方

constructorのパラメーターに必ずprivateやpublicを指定して書くことで、初期化の処理を省略できる。

class Animals {
  constructor(private name: string, private age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

readonly修飾子で読み込み専用にする(書き込めない)

classの中でも外でも、初期化の処理後は参照することはできても、書き込むことができなくなる。また、privateやpublicのあとにreadonlyとつける必要がある。(順序が逆になるとエラー)

class Animals {
  constructor(private readonly name: string, private readonly age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

readonlyは読み込み専用ではあるが、constructorの中はまだ初期化する段階なので、変更することは可能。

class Animals {
  constructor(private readonly name: string, private readonly age: number) {
    this.name = 'キリン'; //ここはreadonlyになっていても変更できる
    this.age = 90; ////ここはreadonlyになっていても変更できる
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

classを継承する

共通する変数だったり、メソッドを持っている場合、classはextendsを使って継承できます。

class Animals {
  constructor(private name: string, private age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

class Zoo extends Animals {} //Animalsクラスを継承する

const zoo = new Zoo('さる', 4); //Animalsクラスにはnameとageがあるため、同じように指定する必要がある
zoo.intro(); //継承しているメソッドも使用できる

ZooのclassにAnimalsのclassを継承させているので、Animalsが持っているnameとageは、Zooでもインスタンス化するときに必要になる。無いとエラーになります。

classを継承しつつ、さらに何かを加えるときは?

他のクラスを継承するだけではなく、さらに何か追加したい場合はsuperをつける必要があります。

class Animals {
  constructor(private readonly name: string, private readonly age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

class Zoo extends Animals {
  constructor(name: string, age: number, public gender: string) {
    super(name, age);
  }
}

const zoo = new Zoo('さる', 4, 'オス');

Animalsのクラスを継承したZooのconstructorには、必ず継承元の引数を指定する必要がある。今回だとnameとageはAnimalsのclassにもあるため、Zooのclassでも指定する。そしてsuper関数の引数にもnameとageを指定すること。そして追加したいものがあれば、constructorの中に追加していく。

継承元のメソッドを上書きしたいとき

Animalsにあるintroメソッドで表示させる内容をZooでは変更したい場合、introメソッドを上書きして指定する必要があります。

ただし、以下のように指定するとAnimalsのクラスでnameとageはprivate修飾子を指定しているためエラーになってしまいます。

class Animals {
  constructor(private readonly name: string, private readonly age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

class Zoo extends Animals {
  constructor(name: string, age: number, public gender: string) {
    super(name, age);
  }
  intro() {
    //nameもageもAnimalsクラスではprivateが指定されているため、エラーになる
    console.log(`この動物は${this.name}です。年齢は${this.age}です。性別は${this.gender}です。`);
  }
}

const zoo = new Zoo('さる', 4, 'オス');

継承先でもAnimalsクラスの変数を使いたい場合は、private修飾子ではなく、protected修飾子を使います。継承先では使えますが、それ以外では使うことができません。

class Animals {
  constructor(protected readonly name: string, protected readonly age: number) {
  }
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。`);
  }
}
const lion = new Animals('ライオン', 12);

class Zoo extends Animals {
  constructor(name: string, age: number, public gender: string) {
    super(name, age);
  }
  intro() {
    //エラーが出なくなる
    console.log(`この動物は${this.name}です。年齢は${this.age}です。性別は${this.gender}です。`);
  }
}

const zoo = new Zoo('さる', 4, 'オス');

classでgetを使う(ゲッター)

getを実行したタイミングで、何かしら処理をさせるもの。
必ずreturnがないとエラーになる。

getとsetは同じプロパティ名にすることができるが、型を統一する必要がある。同じ名前なのにgetがstring型に対して、setがnumber型はエラーになる。

class Animals {
	get colorChange() {
	  if (!this.color) { //もしcolorが空なら
		throw new Error('色の指定がありません');
	  } else {
		return this.color;
	  }
	}
	constructor(private name: string, private age: number, private color: string) {}
	intro(this: Animals) {
	  console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
	}
  }
  const lion = new Animals('ライオン', 12, '赤');
  console.log(lion.colorChange);

classでsetを使う

setでは最低1つのパラメーターを持つ必要があります。

getとsetは同じプロパティ名にすることができるが、型を統一する必要がある。同じ名前なのにgetがstring型に対して、setがnumber型はエラーになる。

class Animals {
	set colorChange(value: string) {
	  if (!value) {
		throw new Error('色の指定がありません');
	  } else {
		this.color = value;
	  }
	}
	constructor(private name: string, private age: number, private color: string) {}
	intro(this: Animals) {
	  console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
	}
  }
  const lion = new Animals('ライオン', 12, '赤'); //ここはcolorを赤で指定している
  
  lion.colorChange = '黒'; //色を黒に変更
  lion.intro(); //「この動物はライオンです。年齢は12です。色は黒です。」と表示される

staticを使って、インスタンスを作らずにclassを使う

classで書いた内容をnewすることでインスタンス化され、設定した値を使うことができますが、インスタンス化せずにclassを使う場合はstaticを使います。

class Animals {
  static infomation = 'ここはAnimalsのクラスです';

  constructor(private name: string, private age: number, private color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
}
//new Animalsなくてもclassにアクセスできる
console.log(Animals.infomation); //「ここはAnimalsのクラスです」と表示される

メソッドも同じく作れます。

class Animals {
  static AdultJudge(age: number) {
    if (age > 20) return true;
    return false;
  }
  constructor(private name: string, private age: number, private color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
}
//new Animalsなくてもclassにアクセスできる
console.log(Animals.AdultJudge(30)); //trueと表示される

継承先でもstaticは使える

Animalsのclassを継承しているZooのclassからも、Animalsのclassにあるstaticにアクセスできる。

class Animals {
  static infomation = 'ここはAnimalsのクラスです';
  static AdultJudge(age: number) {
    if (age > 20) return true;
    return false;
  }
  constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
}

class Zoo extends Animals {
  constructor(name: string, age: number, color: string, public gender: string) {
    super(name, age, color);
  }
  intro() {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。性別は${this.gender}です。`);
  }
}

console.log(Animals.infomation); //ここはAnimalsのクラスです
console.log(Animals.AdultJudge(30)); //true
console.log(Zoo.infomation); //ここはAnimalsのクラスです
console.log(Zoo.AdultJudge(30)); //true

classの中からstaticを操作するには?

classの中でstaticにアクセスする場合、staticのメソッドについては、thisでもクラス名でも参照可能です。通常のメソッドではクラス名でしか参照できません。

class Animals {
  static infomation = 'ここはAnimalsのクラスです';
  speach() {
    console.log(Animals.infomation); //クラス名でしか参照できない
  }
  static call(){
    console.log(this.infomation); //thisでも参照できる
    console.log(Animals.infomation); //クラス名でも参照できる
  }
  constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
}

Abstractクラスを使って、継承先のみで使えるclassを作る

継承先でのみ使うclassなので、newでインスタンス化させません。(できません)

以下の例では、継承先で書いたメソッドを、継承元で実行させています。

abstract class Animals { //継承元

  constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
    this.call(); //ここで実行している
  }
  abstract call(): void;
}

class Zoo extends Animals { //継承先

  call() { //このメソッドをAnimalsクラス側で実行したい
    console.log(`この子のニックネームは${this._nickName}です`);
  }

  get nickName() {
    if (!this._nickName) {
      throw new Error('この子にニックネームはありません');
    } else {
      return this._nickName;
    }
  }

  constructor(name: string, age: number, color: string, public gender: string, protected _nickName: string) {
    super(name, age, color);
  }
}
const zoo = new Zoo('さる', 4, 'レッド', 'オス', 'アパー');
zoo.intro(); //Zooクラスにはintroメソッドはないが、Animalsクラスから継承しているので使用できる

継承先であるZooクラスにはcallメソッドを定義しておきます。(11行目)
このメソッドを継承元のAnimalクラスで実行するためには、Animalクラスの先頭にabstractと指定します。(1行目)

次にAnimalsクラスの中に確実にcallメソッドがあると認識させるようにabstructとcallメソッドを書きます。(7行目)

あとはAnimalsクラスの中の実行したい場所に、Zooクラスに定義したcallメソッドを指定することができます。(6行目)

インスタンスを1つしか生成させないようにする(シングルトンパターン)

インスタンスを1つだけしか作れないようにして、かつ外部からもアクセスできるようにすることをシングルトンパターンといいます。

class Animals {
  private constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
}
//const animals = new Animals('ライオン', 12, '赤'); 外部でインスタンス化ができなくなった

まずはインタンスを外からアクセスできないようにconstructorにprivate修飾子を指定します。これでclassの外側からnewによるインスタンス化ができなくなりました。

class Animals {
  private constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
  static getInstance() {
    const animalsInstance = new Animals('ライオン', 12, '赤');
    return animalsInstance;
  }
}
const animals = Animals.getInstance();

次にインスタンスを作らなくてもアクセスできるstaticをつけたgetInstanceメソッドを作り、その中でAnimalsクラスをインスタンス化させます。

getInstanceメソッドは11行目で実行しています。

class Animals {
  private static instance: Animals;
  
  private constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
  static getInstance() {
    if (Animals.instance) return Animals.instance;
    Animals.instance = new Animals('ライオン', 12, '赤');
    return Animals.instance;
  }
}
const animals = Animals.getInstance();

インスタンスが作成された場合、そのインスランスを保持するためにprivate修飾子とstaticをつけたinstanceプロパティを書いておきます。そのinstanceプロパティ自体はAnimalsクラスを指しているので型もAnimalsを指定します(2行目)

Animalsのインスタンスがすでに作られていれば、いまあるインスタンスを返し、無い場合はインスタンスを作る、という条件分岐を書いています。(9〜11行目)

class Animals {
  private static instance: Animals;
  
  private constructor(protected name: string, protected age: number, protected color: string) {}
  intro(this: Animals) {
    console.log(`この動物は${this.name}です。年齢は${this.age}です。色は${this.color}です。`);
  }
  static getInstance() {
    if (Animals.instance) return Animals.instance;
    Animals.instance = new Animals('ライオン', 12, '赤');
    return Animals.instance;
  }
}
const animals = Animals.getInstance();
animals.info(); //インスタンスにアクセスできる!

これでインスタンスは1つのみ作られ、内部からも外部からもインスタンスにアクセスできるようになります。