아직도 JSON.parse(JSON.stringify())로 깊은 복사하고 있다면

아린·2026년 4월 17일
post-thumbnail

📝 TL;DR

structuredClone()은 네이티브 깊은 복사 API다. Date, Map, Set, 순환 참조까지 처리하고, 라이브러리도 필요 없다.

JSON.parse(JSON.stringify())의 함정

흔히 겪는 시나리오가 있다. API에서 받아온 객체를 깊은 복사해서 폼 초기값으로 세팅했다. 나중에 "변경사항 비교"를 하려고 원본과 현재 값을 비교하는데, 원본의 createdAt이 Date가 아니라 문자열이 되어있다. .getMonth()를 호출하면 TypeError. 원인은 이 한 줄이다.

const copy = JSON.parse(JSON.stringify(original));

직관적이라 관성적으로 쓰게 되지만, 이 방식은 JSON으로 변환할 수 없는 값을 전부 날려버린다.

const original = {
  date: new Date('2026-04-17'),
  tags: new Set(['TIL', 'JS']),
  metadata: new Map([['key', 'value']]),
  score: Infinity,
  optional: undefined,
};

const copy = JSON.parse(JSON.stringify(original));
console.log(copy);
// {
//   date: "2026-04-17T00:00:00.000Z",  ← 문자열로 변환됨
//   tags: {},                            ← Set이 빈 객체로
//   metadata: {},                        ← Map이 빈 객체로
//   score: null,                         ← Infinity가 null로
//                                        ← undefined는 아예 사라짐
// }

Date가 문자열이 되고, Set/Map은 빈 객체가 되고, undefined는 증발한다. 여기에 순환 참조가 있으면 TypeError로 아예 터진다.

structuredClone()은 뭐가 다른가

2022년부터 모든 주요 브라우저와 Node.js 17+에서 사용 가능한 네이티브 API다.

const copy = structuredClone(original);

날짜가 살아있다

const obj = { created: new Date('2026-04-17') };
const copy = structuredClone(obj);

console.log(copy.created instanceof Date); // true
console.log(copy.created.getFullYear());   // 2026

JSON.stringify를 거치면 문자열이 되어 .getFullYear() 같은 메서드를 호출할 수 없지만, structuredClone은 Date 객체를 그대로 유지한다.

Map, Set, RegExp도 복사된다

const obj = {
  tags: new Set(['TIL', 'JS']),
  config: new Map([['theme', 'dark']]),
  pattern: /^hello/gi,
};

const copy = structuredClone(obj);
console.log(copy.tags.has('TIL'));          // true
console.log(copy.config.get('theme'));      // 'dark'
console.log(copy.pattern.test('hello!'));   // true

순환 참조도 처리한다

const obj = { name: 'root' };
obj.self = obj; // 순환 참조

// JSON.parse(JSON.stringify(obj)); ← TypeError: Converting circular structure to JSON
const copy = structuredClone(obj);  // 정상 동작
console.log(copy.self === copy);    // true (순환 구조 유지)

그래도 안 되는 것들

만능은 아니다. structuredClone이 복사할 수 없는 타입이 있다.

타입결과
함수DataCloneError 발생
DOM 노드DataCloneError 발생
SymbolDataCloneError 발생
클래스 인스턴스plain object로 변환 (프로토타입 체인 소실)
class User {
  constructor(name) { this.name = name; }
  greet() { return `Hi, ${this.name}`; }
}

const user = new User('arin');
const copy = structuredClone(user);

console.log(copy.name);            // 'arin' (데이터는 복사됨)
console.log(copy instanceof User); // false (프로토타입 소실)
console.log(copy.greet);           // undefined (메서드 없음)

함수가 포함된 객체를 복사해야 한다면 structuredClone은 답이 아니다. 그 경우엔 수동으로 복사 로직을 작성하거나 lodash의 _.cloneDeep을 쓰는 게 맞다.

💡 어디에 쓸까

  • React 상태 업데이트: 중첩 객체를 불변으로 갱신할 때 spread 연산자 3중첩 대신 structuredClone 후 수정
  • API 응답 캐싱: 응답 객체를 저장하기 전 원본과 분리할 때
  • 폼 데이터 스냅샷: "변경사항 있음" 비교를 위한 초기 상태 저장
  • lodash 의존성 제거: _.cloneDeep 하나 때문에 lodash를 쓰고 있었다면, 네이티브로 대체 가능

📚 참고

profile
💻 FE Developer

0개의 댓글