[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は修飾子と型定義につけるものがある。
一見すると同じことをしているように見えるが、違うもの。
1. readonly foo、
readonly 修飾子。fooが読み取り専用であることを意図している。
結果、fooは書き換えられない変数になる。
2. foo: readonly string[]
readonly 型定義。fooが読み取り専用配列であることを意図している。
結果、fooは書き換えられない配列になる。
3. bar: ReadonlyArray<string>
ReadonlyArray 型定義。barが読み取り専用配列であることを意図している。
結果、barは書き換えられない配列になる。
※ 2.と同じ
4. piyo: Readonly<string[]>
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