LoginSignup
47
31

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-17

はじめに

関数編の続きです。

TypeScriptシリーズ全4回連載中です:writing_hand:
知識ゼロから始めるTypeScript 〜基本編〜
知識ゼロから始めるTypeScript 〜関数編〜
知識ゼロから始めるTypeScript 〜クラス編〜 :point_left:今ココ
知識ゼロから始めるTypeScript 〜応用編〜

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

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

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

クラスの型定義

  • クラスのトップレベルで型宣言
  • constructor(関数)の引数にも型宣言
  • constructor(関数)の戻り値の型宣言は行わない(TypeScriptの言語仕様)
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:クラスを継承した小クラスからでもアクセスできる
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クラスを作成してみる

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②は同じ結果になる

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

const me = new Person('foo', 30)
console.log(me)  // Person { name: 'foo', age: 30 }
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)に便利。

// * 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は省略しない方が良いかも
class VisaCard {
  constructor(public readonly owner: string) {
    this.owner = owner
  }
}

let myVisaCard = new VisaCard('foo')

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

静的メンバの定義

  • newキーワードを使って毎回インスタンスを生成しなくてもアクセスできるようになる
  • 静的なメソッドも定義できる
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の呼び出しが必要
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を使用する
  • 抽象メソッドは必ずオーバーライドをする必要がある
  • 抽象メソッドの宣言はシグネチャーと言う
// 抽象メソッドは抽象クラスの中でしか使えない
abstract class Animal {
  abstract cry(): string
}

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

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

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

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

47
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
47
31