深い不変性のパターン
再帰型を使って、階層の深いreadonly構造を実現します。
「深い不変性のパターン」はCoddyKit上の無料TypeScript Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはTypeScript Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 TypeScript Academyコースには全4レッスンが含まれています。
浅い不変性と深い不変性
readonly修飾子は浅い保護です。最上位は保護しますが、ネストされたオブジェクトまでは保護しません。深い不変性とは、すべての階層を下までReadonlyにすることです。実現には追加の作業が必要です。
interface State { readonly user: { name: string }; }
const s: State = { user: { name: 'Ada' } };
// s.user = {...}; // Error (shallow protection)
s.user.name = 'Grace'; // allowed! nested field is mutable
console.log(s.user.name);浅い保護だけでは不十分な理由
状態管理や共有データでは、浅いreadonlyではネストされたフィールドが変更可能なままです。構造の奥深くにある1か所の見落とした変更だけで、微妙で追跡しにくいバグが発生することがあります。
interface Config { readonly db: { host: string; port: number }; }
const c: Config = { db: { host: 'localhost', port: 5432 } };
c.db.port = 9999; // mutates nested data despite readonly
console.log(c.db.port);再帰的なDeepReadonly型
readonlyを再帰的に適用するmapped typeを作成できます。DeepReadonly<T>はすべてのプロパティをたどり、オブジェクトであればその内部にも再帰的に適用します。
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
console.log('DeepReadonly defined');DeepReadonlyの適用
型をDeepReadonlyでラップすると、ネストされたプロパティも不変になります。これでs.user.nameさえ再代入できず、保護が最下層まで及びます。
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
type State = DeepReadonly<{ user: { name: string } }>;
const s: State = { user: { name: 'Ada' } };
// s.user.name = 'x'; // Error now
console.log(s.user.name);Mapped typeの仕組み
[K in keyof T]はすべてのキーを反復処理します。readonlyプレフィックスは各キーを変更できないようにします。条件型のT[K] extends object ? ... : T[K]は、再帰するかプリミティブ型で止めるかを判断します。
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
type Cfg = DeepReadonly<{ a: number; b: { c: string } }>;
const cfg: Cfg = { a: 1, b: { c: 'x' } };
console.log(cfg.a, cfg.b.c);コンパイル時のみの保護
DeepReadonlyは、実行時ではなく型システムで不変性を強制します。JavaScriptのオブジェクトは型付けされていない経路を通じて技術的には変更可能なままですが、型付けされたコードは完全に保護されます。
// Type-level immutability does not freeze the runtime object.
// For runtime guarantees, combine it with Object.freeze.
console.log('Types guard your code; freeze guards runtime');実行時にネストされたオブジェクトを凍結する
実行時にも不変にするには、Object.freezeを使います。プロパティの追加、削除、変更を防ぎますが、readonlyと同様、デフォルトでは浅い保護です。
const config = Object.freeze({ host: 'localhost', port: 8080 });
console.log(config.port);
// In strict mode, config.port = 1 throws; otherwise silently ignored.Deep Freezeヘルパー
実行時に深く凍結するには、ネストされたオブジェクトを再帰的にたどり、それぞれを凍結します。型レベルのDeepReadonlyと自然に組み合わせられ、最初から最後まで不変性を保てます。
function deepFreeze<T>(obj: T): T {
Object.values(obj as any).forEach((v) => {
if (v && typeof v === 'object') deepFreeze(v);
});
return Object.freeze(obj);
}
const frozen = deepFreeze({ a: { b: 1 } });
console.log(frozen.a.b);より安全な状態のための不変性
深い不変性は、予測可能な状態管理の基盤です。データをその場で変更できなければ、更新時には新しいオブジェクトを作る必要があるため、変更が明示的になり、追跡しやすくなります。
const state = { count: 0, user: { name: 'Ada' } };
// Immutable update: create a new object instead of mutating
const next = { ...state, count: state.count + 1 };
console.log(state.count, next.count);不変更新パターン
Readonlyデータでは、スプレッド構文を使って新しいバージョンを作成します。更新ごとに新しいオブジェクトが作られるため、以前の状態はそのまま保たれます。これは、元に戻す操作、タイムトラベルデバッグ、変更検出に非常に役立ちます。
type State = { readonly items: readonly string[] };
const s: State = { items: ['a'] };
const updated: State = { items: [...s.items, 'b'] };
console.log(s.items, updated.items);型と実行時の安全性を組み合わせる
最も強力な方法は、両方を使うことです。DeepReadonlyでコンパイル時の保証を得て、deepFreezeで実行時にも強制します。これらを組み合わせると、意図しない変更をほぼ不可能にできます。
function freeze<T>(obj: T): T {
return Object.freeze(obj);
}
const settings = freeze({ theme: 'dark', version: 2 });
console.log(settings.theme, settings.version);クイックチェック
深い不変性についての理解度を確認しましょう。
まとめ:深い不変性
次のことを学びました。
readonlyは浅い保護であり、ネストされたフィールドは変更可能なままです。- 再帰的な
DeepReadonly<T>mapped typeは、すべての階層にreadonlyを適用します。 - これはコンパイル時のみの機能です。実行時に強制するには、(深く)
Object.freezeを使います。 - 不変性により、コピーして更新するパターンを通じて、安全で予測可能な状態管理が可能になります。
これで、高度な型と不変性を扱うTypeScript Academyのトラックは完了です。
type State = { readonly value: number };
const s: State = { value: 1 };
const next: State = { ...s, value: s.value + 1 };
console.log(s.value, next.value);よくある質問
「深い不変性のパターン」レッスンは無料ですか?
はい。「深い不変性のパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、TypeScript Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 TypeScript Academyコースには全4レッスンが含まれています。
「深い不変性のパターン」で何を学びますか?
再帰型を使って、階層の深いreadonly構造を実現します。 ブラウザで直接実行するハンズオンコードでTypeScript Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
TypeScript Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのTypeScript Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「深い不変性のパターン」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このTypeScript Academyレッスンでコードを書いて実行できますか?
はい。すべてのTypeScript Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。