1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rubyのattr_reader的なことをTypeScriptでやるならreadonly

Last updated at Posted at 2019-11-30

はじめに

TypeScriptで「クラスにgetterは作るけどsetterを作らない」とき、Rubyのattr_readerっぽくシンプルに書きたいなーと思って試したメモです。

TL;DR

TypeScript
class Hoge {
  constructor(readonly value: Type) { }
}
Ruby
class Hoge
  attr_reader :value
  def initialize(value)
    @value = value
  end
end

なんとTypeScriptの方が短く書ける!

やりたいこと

Itemクラスを、こんな風に使いたい。

TypeScript
const item = new Item('Apple')
console.log(item.name) // Apple
item.name = 'Orange' // エラー

TypeScriptでのイマイチな例

冗長なprivate

検索するとよく見る例ですね。Javaにかなり近い雰囲気。

TypeScript
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にしちゃえばコードはかなりシンプルになります。

TypeScript
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だけをシンプルに作ってくれます。

Ruby
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を使います。

TypeScript
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を活用していきましょう :smile:

ではまた!

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?