この投稿では、TypeScriptでclass構文で定義したメソッドをreadonly
にする小技を紹介します。
実はTypeScriptのメソッドはミュータブルなプロパティ
TypeScriptでもJavaScript同様のclass構文でクラスを定義することができます:
class Person {
}
TypeScriptはJSを拡張していて、フィールドにはreadonly
修飾子をつけることができます。これは、フィールドに値を再代入(=上書き)させないようにすることができます。
class Person {
readonly name: string
constructor(name: string) {
this.name = name
}
}
const person = new Person('山田')
person.name = '佐藤' // 再代入しようとする
// エラー: Cannot assign to 'name' because it is a read-only property.(2540)
この例では、name
フィールドがreadonly
なので、これに新しい値の「佐藤」を再代入するコードがコンパイルエラーになっています。
一方、メソッドは次のようにして生やすことができるのですが、メソッドにはreadonly
修飾子をつけることができません:
class Person {
getName() {
return this.name
}
}
メソッドを再代入することは一般的に考えにくいですよね。であれば、きっとTypeScript的にはメソッドは常にreadonly
扱いのはずです……。
と思っていましたが、実は**メソッドはもろにミュータブル(再代入可)**なのです!
このコードを見てください:
class Person {
constructor(private readonly name: string) {}
getName(): string {
return this.name
}
}
const person = new Person('山田')
console.log(person.getName())
//=> 山田
// ↓ メソッドを再代入するコード
person.getName = () => '佐藤' // コンパイル通る!
console.log(person.getName())
//=> 佐藤
このコードはPerson
のgetName
メソッドを置き換えていますが、コンパイルエラーになることはありません。
調べてみると、TypeScriptのイシューに「メソッドにもreadonlyがほしい」という要望は上がっているようですが、今のところ対応される様子はありません。
メソッドをreadonly
にする小技
メソッドをreadonly
にする方法としては、ファクトリメソッドを作る方法があります。
まず、クラスを直接new
されると、メソッドを守りようがないので、コンストラクタはprivateにします。
その上で、インスタンスを作るためのメソッド(=ファクトリメソッド)を定義します。そして、ファクトリメソッドの戻り値の型をユーティリティ型Readonly<T>
にして、返されるインスタンスをイミュータブルな型にします。
先ほどのPerson
クラスでこれを実践すると次のようなコードになります:
class Person {
// コンストラクタを直接呼べないようにする
private constructor(private readonly name: string) {}
// ファクトリメソッド
static readonly new = (name: string): Readonly<Person> => new Person(name)
getName(): string {
return this.name
}
}
こうすることで、メソッドの再代入がコンパイル時に不可能なクラスを作ることができます:
最後までお読みくださりありがとうございました。Twitterでは、Qiitaに書かない技術ネタなどもツイートしているので、よかったらフォローお願いします→Twitter@suin