JavaScriptでは、スプレッド演算子(...)はオブジェクトのコピーや拡張を便利にするために導入され、配列には長い間使用されてきましたが、ES2018以降はオブジェクトにも使用できるようになりました。TypeScriptでは、スプレッドは単なる構文の改善にとどまらず、オブジェクトを操作する際の型付けのための洗練されたツールとなりました。
歴史的には、JavaScriptでオブジェクトのクローンまたは拡張を行う際にはObject.assignを使用していましたが、このアプローチは型の安全性を失う原因となり、オブジェクトの構造が複雑な場合には鍵の衝突の危険がありました。
問題は、スプレッドを使用してオブジェクトを結合・拡張する際に、TypeScriptが入力構造に基づいて新しい型を導出し、鍵の衝突を処理し(「最後のものが勝つ」)、開発者が意図したものとは異なる結果を生むことがあるという点です。特に、オプショナルなフィールド、readonly、およびクラス内のプライベートプロパティに関する交差に注意が必要です。
解決策:スプレッドを使用して、TypeScriptコンパイラに結果を自動的に導出させることです。複雑なケースでは、入力構造の型を明示的に制限し、型の構造の変更を注意深く監視することが重要です。
コード例:
interface User { name: string; age: number; } interface Extra { isAdmin?: boolean; readonly city: string; } const base: User = { name: 'Ivan', age: 28 }; const extended: User & Extra = { ...base, city: 'Moscow', isAdmin: true };
主な特徴:
スプレッド継承を使用してTypeScriptでオブジェクトの「ディープコピー」を取得できますか?
回答:いいえ。スプレッドは浅いコピー(shallow copy)しか行わず、入れ子のオブジェクトは参照のままです。
const original = { user: { name: 'Anna' } }; const cloned = { ...original }; cloned.user.name = 'Maria'; // original.user.nameも変更されます
スプレッドオブジェクトのキーが重複している場合、型は狭くなるか広がるか、そしてそれが変数の注釈にどのように影響を与えますか?
回答:スプレッドの際には、変数の型が広がり、重複している鍵の場合には右側のプロパティが勝ちます;明示的な型注釈がある場合、型が互換性がないとエラーが発生する可能性があります。
const a = { id: 4, value: "abc" }; const b = { value: 123 }; const c: { id: number; value: number } = { ...a, ...b }; // ok
スプレッドはクラスに適用できますか?プライベートプロパティはどうなりますか?
回答:スプレッドはクラスのパブリックプロパティにのみ適用されます。プライベート(private/#)および保護されたprotectedフィールドは結果のオブジェクトに含まれません。
class Person { private id = 77; name = "Bob"; } const p = new Person(); const spreaded = { ...p }; // spreaded: { name: string }
開発者は、ユーザーオプションを拡張するためにsettingsオブジェクトの値をスプレッドします:
const common = { theme: 'light', notifications: true }; const user = { notifications: false, signature: 'Sasha' }; const merged = { ...common, ...user };
mergedの型が整合することを期待していますが、無意識にsignatureフィールドの型の誤りを犯し、スプレッドが単に鍵をコピーするだけで値を検証しないことを忘れます。
利点:
欠点:
設定の統合には、バリデーション後にのみスプレッドを使用し、戻り値の型を注釈します:
interface Settings { theme: "light" | "dark"; notifications: boolean; signature?: string; } function getSettings(common: Settings, specific: Partial<Settings>): Settings { return { ...common, ...specific }; }
利点:
欠点: