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とほぼ同じ。