TypeScript における配列のコピー(浅いコピーと深いコピー)
TypeScript で配列をコピーするとき、浅いコピー(shallow copy)と深いコピー(deep copy)の違いを理解しておく必要がある。
浅いコピー
浅いコピーは配列の第一階層だけを複製する。プリミティブ値の配列であれば問題ないが、オブジェクトや配列を要素に持つ場合は参照が共有される。
const original = [1, 2, 3];
// スプレッド構文
const copy1 = [...original];
// Array.from
const copy2 = Array.from(original);
// slice
const copy3 = original.slice();スプレッド構文が最も簡潔で、現代の TypeScript では標準的な書き方である。Array.from は配列風オブジェクト(NodeList など)を配列に変換する用途でも使える。slice は引数なしで呼ぶと全要素のコピーを返す。
ネストした配列の問題
浅いコピーでは内部のオブジェクトや配列は同じ参照を指す。
const nested = [[1, 2], [3, 4]];
const shallowCopy = [...nested];
shallowCopy[0][0] = 100;
console.log(nested[0][0]); // 100(元の配列も変わる)内部配列 [1, 2] への参照がコピー先と共有されているため、片方を変更するともう片方にも影響する。
深いコピー
深いコピーはネストしたすべての階層を再帰的に複製する。
const nested = [[1, 2], [3, 4]];
// structuredClone(モダンな方法)
const deepCopy = structuredClone(nested);
deepCopy[0][0] = 100;
console.log(nested[0][0]); // 1(元の配列は変わらない)structuredClone は ES2022 以降のランタイムで利用でき、循環参照も正しく処理する。ただし関数や Symbol、DOM ノードなど一部の値はコピーできない。
古い環境や structuredClone が使えない場合は JSON.parse(JSON.stringify(obj)) という方法もあるが、undefined や関数、Date オブジェクトなどが正しく扱われない点に注意が必要である。
型付き配列のコピー
TypeScript では型情報がコピー先にも引き継がれる。
type User = { name: string; age: number };
const users: User[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
const usersCopy: User[] = structuredClone(users);コピー後の配列も User[] 型として扱われ、型安全性が維持される。