はじめに
TypeScriptで「クラスにgetterは作るけどsetterを作らない」とき、Rubyのattr_reader
っぽくシンプルに書きたいなーと思って試したメモです。
TL;DR
class Hoge {
constructor(readonly value: Type) { }
}
class Hoge
attr_reader :value
def initialize(value)
@value = value
end
end
なんとTypeScriptの方が短く書ける!
やりたいこと
Item
クラスを、こんな風に使いたい。
const item = new Item('Apple')
console.log(item.name) // Apple
item.name = 'Orange' // エラー
TypeScriptでのイマイチな例
冗長なprivate
検索するとよく見る例ですね。Javaにかなり近い雰囲気。
class Item {
private _name: string
constructor(name: string) {
this._name = name
}
get name(): string {
return this._name
}
}
const item = new Item('Apple')
console.log(item.name) // Apple
item.name = 'Orange' // Cannot assign to 'name' because it is a read-only property.
うーん、、ちょっとつらい。
やりたいことは「コンストラクタの引数をそのまんまgetしたい」だけなのに、this
が必須だったり、名前が被るから_
つけて区別したり。。
無法地帯なpublic
コンストラクタの引数にアクセス指定子を追加すると、同じ名前でメンバ変数を作ってくれます。ここをpublic
にしちゃえばコードはかなりシンプルになります。
class Item {
constructor(public name: string) { }
}
const item = new Item('Apple')
console.log(item.name) // Apple
item.name = 'Orange'
console.log(item.name) // Orange
この書き方はとても好きなのですが、setter以前のメンバ変数直代入を認めるので、使う側はもう無法地帯。。ちょっとこれで妥協はしたくないですねぇ。
Rubyの素敵な例
Rubyにはattr_reader
ってのがあって、getterだけをシンプルに作ってくれます。
class Item
attr_reader :name
def initialize(name)
@name = name
end
end
item = Item.new('Apple')
puts item.name #=> Apple
item.name = 'Orange' #=> undefined method `name=' (NoMethodError)
Rubyはそもそもインスタンス変数にクラス外から直アクセス禁止(Hoge.new().@fuga
みたいのは不可)なので、単純比較はできないけどこれはとっても素敵。
TypeScriptでもこんな風に書けないものか!?
TypeScriptの素敵な例
書けました!readonly
を使います。
class Item {
constructor(readonly name: string) { }
}
const item = new Item('Apple')
console.log(item.name) // Apple
item.name = 'Orange' // Cannot assign to 'name' because it is a read-only property.
シンプルで安全で良いですね!
readonlyとは?
http://www.typescriptlang.org/docs/handbook/interfaces.html#readonly-properties
このreadonly
は、どんなアクセス指定子とも組み合わせて使えます。
public readonly hoge: string
protected readonly fuga: string
private readonly piyo: string
たとえばprivate readonly
の場合は、「クラス内のメソッドでも変更不可(コンストラクタでの初期化のみ)」となります。
class Hoge
private readonly hoge: string
constructor(public _hoge: string) {
this.hoge = _hoge // OK
}
setHoge(_hoge: string) {
this.hoge = _hoge // Cannot assign to 'hoge' because it is a read-only property.
}
}
コンストラクタの引数でも、もちろん同様に使えます。
constructor(public readonly hoge: string) { }
constructor(protected readonly fuga: string) { }
constructor(private readonly piyo: string) { }
TSではアクセス指定子省略はpublic
なので、この2つは等価ってことですね。
constructor(public readonly hoge: string) { }
constructor(readonly hoge: string) { }
今知ったけど、C#にもreadonly
がありました。同じMicrosoftだし、これがTypeScriptにも導入されたのですねきっと。
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/readonly
おわりに
C#使いにとっては当たり前なんでしょうけど、Rubyのattr_reader
と比較する記事は見つからなかったので、これで世界のググラビリティが少し上がったのではないかと思います!
型でキッチリ書くことと、コードが冗長にならないこと、ぜひとも両立をめざしたいものですねっ!TypeScriptではpublic readonly
を活用していきましょう
ではまた!