TypeScriptでは、readonly
修飾子をつけたオブジェクトプロパティを変更することができない。
例えば、次の例のようにreadonly bar
で型アノテーションされた場合、bar
に値を代入しようとするとコンパイル時のチェックでエラーになる。
const foo: { readonly bar: number } = { bar: 1 };
foo.bar = 2; // Cannot assign to 'bar' because it is a read-only property.
しかし、Object.assign
を使うとこのコンパイル時のチェックを回避することができる。
const foo: { readonly bar: number } = { bar: 1 };
Object.assign(foo, { bar: 2 });
console.log(foo); //=> {bar: 2}
TypeScriptのコンパイラ(v3.5)が、現状ではObject.assign
までチェックしていないためだと思われる。これがわざとなのか、ただ未対応なのかは深く追ってないので分からない。
型のチェックも回避できる
ちなみに、Object.assign
を使うと互換しない型も代入することができる。
const foo: { readonly bar: number } = { bar: 1 };
Object.assign(foo, { bar: 'hello' });
console.log(foo); //=> {bar: "hello"}
readonly
をやぶるだけでなく、型も変えられるので、用法用量には注意しよう。
どういうときに使う?
Object.assign
を濫用すると、型安全が脅かされるため、基本的に避けたほうがいいが、場合によっては安全性を高めるために使える場面がある。
たとえば、フレームワークを作るときなんかが考えられる。
typeormの例を見てみよう。TypeScript製のORマッパーだ。typeormのConnection
クラスには、DBに接続済みかのフラグisConnected
プロパティがある。これはreadonly
で宣言されている。typeormのユーザは書き換えることができない。ユーザがうっかり値を上書きするような事故が防げ、安全性が高くなっている。
/**
* Connection is a single database ORM connection to a specific database.
*/
export class Connection {
//...
/**
* Indicates if connection is initialized or not.
*/
readonly isConnected: boolean;
しかし、typeorm自身は接続後にisConnected
をtrue
にしなければならない。そこで、内部的にObject.assign
を使って上書きしている。
export class Connection {
//...
async connect(): Promise<this> {
// ...接続する処理...
ObjectUtils.assign(this, { isConnected: true });
※ObjectUtils
はtypeorm独自実装のオブジェクトだが、やってることはObject.assign
とほぼ同じ。