깊은 복사 얕은 복사에 대하여

robin Han·2025년 4월 20일
2

초반 프로젝트를 진행했을때, 객체를 초기값으로 다시 되돌리는 작업을 했었는데 해당 작업을 Lodash의 cloneDeep()으로 해결했습니다 하지만 해당 코드 하나때문에 라이브러리에 의존하기 싫었고 또한 어떠한 부분이 문제였는지 제대로 생각해보고싶어서 작성했습니다.

문제가 되었던 코드부분

const initialBrideGroom: BrideGroomInformation[] = [
  {
    role: '신랑',
    name: '',
    relation: '아들',
    family: {
      father: { name: '', isDeceased: false },
      mother: { name: '', isDeceased: false },
    },
  },
  {
    role: '신부',
    name: '',
    relation: '딸',
    family: {
      father: { name: '', isDeceased: false },
      mother: { name: '', isDeceased: false },
    },
  },
];

여기서 family안에 들어있는 객체들이 zustand를 사용하는중에 제대로 초기화가 되지않는 문제가 생겼습니다.

확인 해야하는것

1. reset 함수를 제대로 구현했는가 ?

기존에 작성한 reset 함수

 reset: () => {
    set({ brideGroom: initialBrideGroom });
  }

다른 스토어에있는 상태값들은 제대로 초기화되고 해당 상태의 밖에있는 속성값들은 제대로 초기화가 되기때문에 clone의 문제라고 생각되었다.

2. 제대로 객체가 복사되고 수정되었는가?

얕은 복사 깊은 복사 문제 같은데 어디서 부터 잘봇 된거지 ?
우선 이걸 사용중인 zustand는 얕은 복사를 사용하고 있다 이게 문제인가 ?
reset 함수를 실행 시키때 해당 안에 있는 원시값들은 변경이 되수있겠지만 그 안에있는 객체는 다른 하나의 참조값들이라서 여전히 메모리에 남아있는 참조값을 가지고 오기때문에 독립적인 값들로 초기화를 시켜줘야한다
따라서 깊은 복사 필요하다고 생각...

3. 그럼 깊은 복사 할수있는 방법은 ?

방법 1: JSON.parse(JSON.stringify(...)) 사용

JSON.stringify(obj) → 객체를 JSON 문자열로 바꿈

JSON.parse(...) → 그 문자열을 다시 객체로 바꿈
즉 원시값에 참조값을 바꿔주기떄문에 메모리에 남아있는 참조값을 사용하지않음
하지만 문제열로 바꾸기 때문에
undefined, function, Symbol → 무시됨
Date → 문자열로 변환되어 원래 타입 정보 사라짐
Map, Set, RegExp, Error, BigInt → 무시 or 오류
순환 참조 에러 ...

reset: () => {
  set({ brideGroom: JSON.parse(JSON.stringify(initialBrideGroom)) });
}

방법 2: structuredClone() 사용 (최신 브라우저 + Node 17+)

네이티브 API 브라우저에서 처리 원래 자료형, 참조 구조, 순환 참조까지 완전히 유지하며 복사
structured clone algorithm 사용 재귀적으로 순회하면서 복제하고, 각각의 타입과 구조를 유지해서 새로운 객체를 생성

reset: () => {
  set({ brideGroom: structuredClone(initialBrideGroom) });
}

방법 3: Lodash CloneDeep() 사용

reset: ()=>{
	set({ brideGroom: cloneDeep(initialBrideGroom) });
}

방법 4: I'm too Lazy to think

reset: () => {
    set(() => ({
      brideGroom: [
        {
          role: '신랑',
          name: '',
          relation: '아들',
          family: {
            father: { name: '', isDeceased: false },
            mother: { name: '', isDeceased: false },
          },
        },
        {
          role: '신부',
          name: '',
          relation: '딸',
          family: {
            father: { name: '', isDeceased: false },
            mother: { name: '', isDeceased: false },
          },
        },
      ],
    })

해당 방법들을 공부하다보니 Pollyfill이라는 개념을 알게되었다
브라우저나 JS 런타임이 지원하지 않는 기능을, 직접 구현해서 대신 동작하게 만들어주는 코드
즉 오래된 브라우저에서 지원하지 않는 기능들을 직접 구현해서 채워주는 스크립트 (fetch, promise structuredClone 등)

//Array.include 메서드가 존재하지않으면 직접 구현 
if (!Array.prototype.includes) {
 Array.prototype.includes = function (element) {
   return this.indexOf(element) !== -1;
 };
}

Pollyfill :기능이 없을 때만 조건부로 정의해서 기존 브라우저 호환성을 확보
Transpile :코드를 다른 버전/형태의 코드로 변환 TypeScript → JavaScript 또는 ES6+ → ES5
Shim : Polyfill과 비슷하지만, 기존 기능을 덮어쓰는 경우도 포함

0개의 댓글