📖

Readonly 使ってますか?

2024/06/12に公開

ムーザルちゃんねるのムーです。今回は TS の Readonly を使うか否か zaru さんと会話しました。

以前の zaru さんのこちらの記事 Reactのpropsはreadonlyにするべきか?で、Readonly については記載していますが、今回会話をする中で改めて気づいた知見などがあったのでシェアしたいと思います。

https://www.youtube.com/watch?v=LHRzYXgxqxk

動画本編の中でも Readonly の罠として二つ紹介していますが、それらについて書いておきたいと思います。

その1 immutable なオブジェクトを mutable なものにアサインすると、変更できてしまう。

これはよく知られている挙動だと思います。

具体的には以下のようなコードです。

interface Cat {
  readonly name: string;
}

const cat1: Cat = { name: "Alice" };

cat1.name = "Bob"; // エラーになる

const cat2 = cat1;
cat2.name = "Bob"; // これもエラーになる

const cat3: { name: string } = cat1;
cat3.name = "Bob"; // これはパスしてしまう!

console.log(cat1.name); // Bob になってる

このように、immutable なオブジェトのつもりでも、実際はプロパティを変更できてしまいます。

この挙動を防ぐ --enforceReadonly

今回の知見の一つが、この挙動を防ぐ(つまり immutable のままにしてくれる)オプション --enforceReadonly です。これは現在開発中のようです。

https://github.com/microsoft/TypeScript/pull/58296

このオプションが使えるようになれば、上記のようなアサインメントで困ることもなくなります。これは、とてもよさそうなオプションですね!

その2 分割代入すると readonly が消える。

これは、あまり記事ではみかけないのですが、挙動としては以下です。

interface Cat {
  readonly nickname: string;
}

const cat1: Cat = { nickname: "Alice" };

let { nickname } = cat1;

nickname = "bob"; // エラーにならない

上のコードを見ればそれはそうだろ思うかもしれませんが、実際よくあるコードとして、関数の引数を分割代入で受けるケースです。

interface Cat {
  readonly nickname: string;
}

function foo({ nickname }: Cat) {
  nickname = "Bob"; // エラーにならない
}

このように書くと、nickname の readonly は効かないので、意味がないコードを書いていることになりますので、注意が必要かもしれません。

ネストされたプロパティはどうなる?

ここで話しが終わると全ての readonly が無効化されてしまうのかと思う方もいると思うのですが、そうではありません。もう少し踏み込んでみてみます。

nest されたオブジェクトの readonly は残ります。どういうことかというと以下のようなコードで見てみましょう。

interface Cat {
  readonly nickname: string;
  item: {
    readonly name: string;
  }
}

const cat1: Cat = { nickname: "Alice", item: { name: "Toys" } };
const { nickname, item } = cat1;

item.name = "Boxes"; // これはエラーになってくれる

このような挙動は、そもそも、readonly はオブジェクトのプロパティについて設定できるもので、プリミティブな値に対しては付与できるものではないから、この挙動は自然なものかなと、僕は解釈しています。(この説明では言葉不足だと思うのですが。)

Readonly の振る舞いについての詳しい紹介は以上です!
この知見がだれかのお役に立てば幸いです。

ちなみに、話しを動画のタイトルにもどしますと、僕らの Readonly の利用状況は以下でした。

moo 「良いものとわかっているが、ノイズになるので書いてない」
zaru 「今は書いていないけど、--enforceReadonly が使えるようになったら、基本全部に書いていきたい」
2人 「チーム開発では、ある程度使う使わないの指針が決まってる、バラバラしなくてよいかもね」

おまけ

React の props は分割代入でうける? props そのまま? という趣味の話しがありますが、これは readonly の話と少し絡みます。(react の props には readonly が搭載されているため。)
この話についても、機会があればしてみたいと思います。

ムーザルちゃんねる

Discussion