VueのDeepReadonly型はImmutableな値を示す型です。
しかしDeepReadonly型は少し問題があり、例えばDeepReadonly<Ref<T>>型に対して普通のRef<T>が代入できてしまいます。これはTypeScriptが構造的型付けという手法を取っているために発生する問題です。
具体的に言うと、DeepReadonly<Ref<T>>はRef<T>とプロパティ構造が一致しているため、コンパイラは代入を許してしまいます。表面的には「readonly」とついているのに、実際には.valueを書き換えることができてしまうのです。つまり型レベルでImmutableを保証しているつもりでも、実際には完全には守られないというギャップが生じます。
この挙動はVueの型定義だけでなく、TypeScript自体の設計に起因しています。TypeScriptは「構造的型付け」を採用しており、名前や宣言の違いではなく「型の形」が一致するかどうかで代入可能性を判断します。そのためDeepReadonly<Ref<T>>とRef<T>の間に本質的な違いがないとみなされ、結果的に代入が許されてしまうのです。
では、どう対処すべきでしょうか。一つの有効な方法は「ブランド型(brand type)」を使うことです。ブランド型とは、通常の型に“識別用の印”を付与することで、構造的に同じ型であっても別物として扱わせる仕組みです。TypeScriptは構造的型付けを採用しているためにDeepReadonly<Ref<T>>
とRef<T>
を同一視してしまいますが、ブランドを導入すれば「これは通常のRefとは異なる特別な型だ」とコンパイラに区別させることができます。
これを利用することで型レベルで保証できます。