[TypeScript] readonly 修飾子 と readonly / ReadOnly / ReadonlyArray の型定義
自分が知らなかったので備忘録として。
問題
export class Hoge {
constructor(
private readonly foo: readonly string[] = [],
private readonly bar: ReadonlyArray<string> = [],
private readonly piyo: Readonly<string[]> = [],
) {}
add(_: string) {
this.foo.push(_);
this.bar.push(_);
this.piyo.push(_);
}
}
addメソッド内のすべての push で TypeScript エラーが出るものの、それ以外は構文として正しい。
上記について見ていく。
解説
readonly
は修飾子と型定義につけるものがある。
一見すると同じことをしているように見えるが、違うもの。
readonly foo
、
1. readonly 修飾子。fooが読み取り専用であることを意図している。
結果、fooは書き換えられない変数になる。
foo: readonly string[]
2. readonly 型定義。fooが読み取り専用配列であることを意図している。
結果、fooは書き換えられない配列になる。
bar: ReadonlyArray<string>
3. ReadonlyArray 型定義。barが読み取り専用配列であることを意図している。
結果、barは書き換えられない配列になる。
※ 2.と同じ
piyo: Readonly<string[]>
4. Readonly 型定義。
string[] に対する定義なので、piyoが読み取り専用配列であることを意図している。
結果、piyoは書き換えられない配列になる。
※ 2.と同じ
ベストプラクティス
readonly
のほうがスペルが短くスッキリして見える印象だが、前述のように同じ綴りでも記述する場所によって効果が違う。
これは開発者がそれに脳内メモリを割かれることにつながるし、
開発現場でありがちなEntityやValueObjectのコピペにおいて、編集ミスで想定外の構成になることもあり得る。
個人的な意見としては、Readonly
とReadonlyArray
を正しく使って明確にわかるようにするほうが良いと思っている。
-
readonly
は禁止 - 配列やタプルの読み取り専用化は
ReadonlyArray
を必ず使う
constructor(private piyo: ReadonlyArray<string>) {}
その他(クラス内でのみpush可能にする)
前述のクラスはreadonly
の説明のためだけに作ったもの。
配列に対する編集が全く出来ないので、ロジックとして実際に使えるシーンは多くない。
下記のように、private
で修飾しクラス内で取り扱えるようにしながら、外部に渡すときは ReadonlyArray
とするのがベストプラクティスだと思う。
export class Hoge {
constructor(private piyo: string[]) {}
get(_: string): ReadonlyArray<string> {
return this.piyo;
}
add(_: string): void {
this.piyo.push(_);
}
}
参考
Discussion