知識ゼロから始めるTypeScript 〜クラス編〜

## はじめに

[関数編](https://qiita.com/yukiji/items/614ff63ade4072731750)の続きです。

TypeScriptシリーズ全4回連載中です:writing_hand:
[知識ゼロから始めるTypeScript 〜基本編〜](https://qiita.com/yukiji/items/52ea9106e254c1a12dea)
[知識ゼロから始めるTypeScript 〜関数編〜](https://qiita.com/yukiji/items/614ff63ade4072731750)
[知識ゼロから始めるTypeScript 〜クラス編〜]
(https://qiita.com/yukiji/items/3db06601ece7f080b0d0) :point_left:今ココ
[知識ゼロから始めるTypeScript 〜応用編〜]
(https://qiita.com/yukiji/items/e890053e62d2f7edc7c7)

クラス編ではTypeScriptの便利機能がもりもりです。

全体を通して一番重要なパートかもしれません。

どのような機能があるか見ていきましょう。

## クラスの型定義

- クラスのトップレベルで型宣言
- constructor(関数)の引数にも型宣言
- constructor(関数)の戻り値の型宣言は行わない(TypeScriptの言語仕様)

```ts
class Person {
  // クラスの型宣言
  name: string
  age: number
  // constructorの引数に型宣言
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  // クラスのメソッド(関数)に型宣言
  profile(): string {
    return `name: ${this.name} age: ${this.age}`
  }
}
let taro = new Person('Taro', 30)

console.log(taro.profile())  // name: Taro age: 30 
```

## アクセス修飾子

TypeScriptではJavaScriptには無いアクセス修飾子が利用できる。

- `public`：全体からアクセスできる
- `private`：クラス内部からのみアクセスできる
- `protected`：クラスを継承した小クラスからでもアクセスできる

```ts
class Person {
  // publicはアクセスの制約がない。省略できる。
  public name: string
  // クラス内部からのみアクセスできる
  private age: number
  // 継承した子クラスからもアクセスできる
  protected nationality: string

  constructor(name: string, age: number, nationality: string) {
    this.name = name
    this.age = age
    this.nationality = nationality
  }

  profile(): string {
    // ageはprivateなのでクラス内部のみアクセスできる
    return `name: ${this.name} age: ${this.age}`
  }
}

let taro = new Person('Taro', 30, 'Japan')

console.log(taro.name)  // Taro
console.log(taro.age)  // ageはprivateなのでコンパイルエラー
console.log(taro.profile())  // privateのageを含むメソッドなのでエラーになる
```

`Person`クラスを継承した`Android`クラスを作成してみる

```ts
class Android extends Person {
  constructor(name: string, age: number, nationality: string) {
    // 親クラスのconstructorメソッドを継承
    super(name, age, nationality)
  }
  profile(): string {
  // ageはprivateなのでアクセスできないのでエラーになる
  // nationalityはprotectedなのでアクセスできる
    return `name: ${this.name} age: ${this.age}, nationality: ${this.nationality} `
  }
}
```

## constructorの便利な書き方

アクセス修飾子を`constructor`メソッドの引数に書くことで、`クラスの型宣言`、`初期化`が可能

この場合`public`修飾子は省略しない

以下`sampe①`と`sample②`は同じ結果になる

```ts:sample①
class Person {
  constructor(public name: string, protected age: number) {}
}

const me = new Person('foo', 30)
console.log(me)  // Person { name: 'foo', age: 30 }
```

```ts:sample②
class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}

const me = new Person('foo', 30)
console.log(me)  // Person { name: 'foo', age: 30 }
```

## getterとsetter

アクセスを制御する時に便利。

- `get`はアクセスだけできる
- `set`は値を設定するときのみ実行される

`private`に設定された変数はアクセス(書き換えも)できないが、アクセスだけしたい時(`get`)、値だけ書き換えたい時(`set`)に便利。

```ts
// * owner
//   * 所有者
//   * 初期化時に設定できる
//   * 途中で変更できない
//   * 参照できる

// * secretNumber
//   * 個人番号
//   * 初期化時に設定できる
//   * 途中で変更できる
//   * 参照できない

class MyNumberCard {
  // 慣習的にアンダースコアでリネームする
  private _owner: string
  private _secretNumber: number

  constructor(owner: string, secretNumber: number) {
    this._owner = owner
    this._secretNumber = secretNumber
  }

  get owner() {
    return this._owner
  }

  set secretNumber(secretNumber: number) {
    this._secretNumber = secretNumber
  }

  debugPrint() {
    return `secretNumber: ${this._secretNumber}`
  }
}

let card = new MyNumberCard('fooさん', 1234567890)

console.log(card.owner)  // fooさん
console.log(card.secretNumber)  // undefined
console.log(card.debugPrint())  // secretNumber: 1234567890
```

## readonly修飾子

- クラスのプロパティを読み取り専用にする時に利用する
- TypeScriptを詳しく無い人向けに`public`は省略しない方が良いかも

```ts
class VisaCard {
  constructor(public readonly owner: string) {
    this.owner = owner
  }
}

let myVisaCard = new VisaCard('foo')

console.log(myVisaCard.owner)  // foo
myVisaCard.owner = 'buzz'  // コンパイルエラー
```

## 静的メンバの定義

- newキーワードを使って毎回インスタンスを生成しなくてもアクセスできるようになる
- 静的なメソッドも定義できる

```ts
class Me {
  static isProgrammer: boolean = true
  static firstName: string = 'Satoru'
  static lastName: string = 'Iguchi'

  static work() {
    // thisを付ける
    return `Hello ${this.firstName}`
  }
}

// クラス名.メンバ変数でアクセスできる
console.log(Me.isProgrammer)  // true
console.log(Me.work())  // Hello
```

## クラスの継承

- 既存のクラスを拡張して新たなクラスを作り上げる
- 継承は`extends`を使用する
- 派生クラスの`constructor`には`super`の呼び出しが必要

```ts
class Animal {
  constructor(public name: string) {}

  run(): string {
    return 'I can run'
  }
}
// Animalクラスを継承
class Lion extends Animal {
  public speed: number
  constructor(name: string, speed: number) {
    super(name)
    this.speed = speed
  }
  run(): string {
    //super.run()で親のrunメソッドを呼び出す
    return `${super.run()} ${this.speed}km/h.`
  }
}

console.log(new Animal('Mickey').run()) // I can run
console.log(new Lion('Simba', 80).run()) // I can run 80km/h.
```

## 抽象クラスと抽象メソッド

- 大規模なアプリケーション開発をする時に便利
- 抽象メソッドは継承するクラスで実装しなければならないので、実装漏れを防ぐ。
- 抽象化は`abstract`を使用する
- 抽象メソッドは必ずオーバーライドをする必要がある
- 抽象メソッドの宣言はシグネチャーと言う

```ts
// 抽象メソッドは抽象クラスの中でしか使えない
abstract class Animal {
  abstract cry(): string
}

class Lion extends Animal {
  cry() {
    return 'roar'
  }
}

// 抽象メソッドが定義されていないのでエラーになる
class Tiger extends Animal {}
```

## インターフェース・リターンズ

- 複数のクラスは継承できないが、インターフェースを使うと多重継承の様なことができる
- 厳密に言うと`継承の様なことができる`のであくまで実装するという
- `interface`で定義したものを`implements`で実装する
- `interface`で定義したメソッドを`implements`で実装したクラスに定義しないとエラーになる

```ts
class Mahotsukai {}
class Souryo {}

// 2つのクラスは継承できない。
class Taro extends Mahotsukai, Souryo {} // エラー

// interfaceの定義
interface Kenja {
  ionazun(): void
}

interface Senshi {
  kougeki(): void
}

// implementsの定義
class Jiro implements Kenja, Senshi {
  // interfaceで定義したメソッドを定義しないとエラーになる
  ionazun(): void {
    console.log('ionazun')
  }
  kougeki(): void {
    console.log('kougeki')
  }
}

const jiro = new Jiro() // インスタンス生成

jiro.ionazun() // ionazun
jiro.kougeki() // kougeki
```

## まとめ

TypeScriptを書き方気持ちが女神級に高まってきました:metal: