tl;dr
const variable = <element> as const
の形で定義する
// object
const immutableObj = {
a: 1,
b: "test message",
inner: { key: "foo" },
} as const;
// =>型
// const immutableObj: {
// readonly a: 1;
// readonly b: "test message";
// readonly inner: {
// readonly key: "foo";
// };
// }
const immutableList = [1, "test message", ['inner']] as const;
// => 型
// const immutableList: readonly [1, "test message",
// readonly ["inner"]
// ]
as const
- ネストされた子要素までreadonlyになる
- 上記だとinnerの箇所
- リテラル型が使われる
- 上記だと
b: string
でなくて、b: "test message"
型になる
- 上記だと
- TypeScriptレイヤーの話なのでトランスパイルされたJSには何も影響がない
- Classは
as const
できない。なので、Object.freeze
を使うか、クラスプロパティにreadonlyを指定する(後述)
Object.freeze
Object.freeze() - JavaScript | MDN
-
子要素までfreezeされない
- そのため、
Object.freeze
を使う+nestされた要素までfreezeする場合はオレオレなdeepFreezeが必要 - (REF: ES6のconstを使い倒すレシピ2 - Object.freeze編 〜 JSおくのほそ道 #035 - Qiita)
const mutableObj = { a: 1, b: "test message", inner: { key: "foo" } }; const freezed = Object.freeze(mutableObj); freezed.b = 'dar' // ERROR freezed.inner.key = "bar"; // SUCCESS
- そのため、
-
JavaScriptレイヤーの実装なので、トランスパイルされたJSにも処理が入る
-
Object.freezeを使ったことによるperformace劣化はほぼない
- REF: ecmascript 5 - Any performance benefit to "locking down" JavaScript objects? - Stack Overflow -
リテラル型の情報がロストしてしまう
const freezed = Object.freeze(
{ a: 1, b: "test message", inner: { key: "foo" } }
);
// => 型
// const freezed: Readonly<{
// a: number;
// b: string; << stringでなくて'test message'型であってほしい
// inner: {
// key: string;
// };
//}>
-
Classでは
as const
でなくてObject.freeze
が使える。- ただ、nestしたオブジェクトに対するfreezeがガバガバなので注意
class Person { public obj: { a: number; b: string, inner: {key: string} }; constructor() { this.obj = { a: 1, b: "hello world", inner: {key: 'foo'} }; } } const p = new Person(); const freezed = Object.freeze(p); freezed.obj = {...freezed.obj, b: 'bar1'}; // ERROR TS-2540 freezed.obj.b = 'new bar'; // SUCCESS freezed.obj.inner = {key: 'bar' }; // SUCCESS
まとめ
- 極力
as const
を使う - Class のときは仕方ないので
Object.freeze
を使う