はじめに
関数編の続きです。
TypeScriptシリーズ全4回連載中です
知識ゼロから始めるTypeScript 〜基本編〜
知識ゼロから始めるTypeScript 〜関数編〜
[知識ゼロから始めるTypeScript 〜クラス編〜]
(https://qiita.com/yukiji/items/3db06601ece7f080b0d0) 今ココ
[知識ゼロから始めるTypeScript 〜応用編〜]
(https://qiita.com/yukiji/items/e890053e62d2f7edc7c7)
クラス編では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②
は同じ結果になる
class Person {
constructor(public name: string, protected age: number) {}
}
const me = new Person('foo', 30)
console.log(me) // Person { name: 'foo', age: 30 }
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を書き方気持ちが女神級に高まってきました