목차
1. 문제 발생 이유🌿 얕은 복사 & 깊은복사2. 깊은복사 방법🌱 JSON.parse(JSON.stringify)🌿 재귀 함수 사용🪴 lodash 라이브러리의 cloneDeep 함수 사용🌳 structuredClone 함수 사용
리코일에서 데이터를 담아 sorting 하던 중 다음과 같은 에러를 직면했다. 콘솔을 찍어볼땐 제대로 값을 받아오는데 sort 가 불가능한 read only (읽기전용) 데이터라고 한다. 이를 해결하기위해 리코일에서 받아온 데이터를 “깊은복사” 하여 해결해야 한다.
1. 문제 발생 이유
🌿 얕은 복사 & 깊은복사
얕은 복사는 객체의 참조값(주소 값)을 복사하고, 깊은 복사는 객체의 실제 값을 복사한다.
자바스크립트에서 값은 원시값과 참조값 두 가지 데이터 타입의 값이 존재한다.
자바스크립트에서 원시 타입(primitive type)의 값은 새로운 메모리 공간에 독립적인 값을 저장하기 때문에
깊은 복사가 되고 참조 타입(reference type)값은 얕은 복사가 된다.
원시 타입과 참조 타입의 가장 큰 차이점은 원본이 바뀌면 참조 타입은 복사본도 같이 변경되지만,
원시 타입은 변경되지 않는다는 점이 큰 차이점이다.
Recoil은 애초에 불변성을 유지하도록 설계되었다고 한다.
2. 깊은복사 방법
🌱 JSON.parse(JSON.stringify)
자바스크립트에서 배열을 정렬할 때 발생하는
TypeError: Cannot assign to read only property '0' of object '[object Array]'
오류는, 배열이 변경 불가능(immutable) 상태로 설정되어 있을 때 발생할 수 있습니다. 이는 특히 React의 상태 관리 라이브러리인 Recoil을 사용할 때 발생할 수 있는데, Recoil 상태(selectors 또는 atoms)로부터 반환된 배열을 직접 변경하려고 할 때 발생할 수 있습니다.이 문제를 해결하기 위해, 원본 배열을 직접 변경하지 않고 깊은 복사(deep copy)를 수행한 후에 정렬 작업을 진행해야 합니다. 깊은 복사는 원본 객체의 완전한 복사본을 생성하여, 원본 객체와는 완전히 독립된 새 객체를 만들게 합니다.
깊은 복사를 수행하는 방법 중 하나는
JSON.parse(JSON.stringify(object))
를 사용하는 것입니다. 하지만 이 방법은 함수, Date
객체, undefined
, RegExp
객체 등 일부 데이터 타입에 대해서는 올바르게 작동하지 않을 수 있습니다. 다행히 여기서는 배열 내 객체들에 대해 단순한 구조를 다루고 있으므로, 이 방법을 사용할 수 있습니다.다음은 깊은 복사를 이용하여
sort
함수를 사용하기 전에 원본 배열을 복사하는 방법을 보여줍니다:const { allCartList } = useRecoilValue(CartDataState) ... <SC.CartBodyWrapper> {allCartList?.map((list, i) => { const { isPrepaid } = list.groupDeliveryPrice // 깊은 복사를 이용하여 배열 복사 const sortedGroupList = JSON.parse(JSON.stringify(list.groupList)).sort((a, b) => a.id - b.id); return ( <SC.CartTableWrapper key={`cart-list-${i}`}> <div> {sortedGroupList.map((item, itemIndex) => { return ( <SC.CartTableItemWrapper key={`cart-list-${i}-${itemIndex}`}> ... </SC.CartTableItemWrapper> ) })} </div> </SC.CartTableWrapper> ) })} </SC.CartBodyWrapper>
이렇게 하면 원본
list.groupList
는 변경되지 않으므로, Recoil 상태를 직접 변경하는 것과 관련된 오류를 피할 수 있🌿 재귀 함수 사용
깊은 복사(deep copy)를 수행하는 다른 방법들이 있습니다.
JSON.parse(JSON.stringify(object))
방법은 매우 간단하고 널리 사용되지만, 몇 가지 한계가 있기 때문에 다른 상황에서는 다른 방법들을 고려할 수 있다.
객체나 배열 내부의 중첩된 객체나 배열까지 깊은 복사를 하기 위해 재귀 함수를 사용할 수 있습니다. 이 방법은 모든 종류의 데이터 타입과 순환 참조를 처리할 수 있도록 조정할 수 있습니다.
javascriptCopy code function deepCopy(obj) { if (obj === null || typeof obj !== 'object') { return obj; } let temp = obj.constructor(); // 배열이면 배열을, 객체면 객체를 생성 for (let key in obj) { temp[key] = deepCopy(obj[key]); } return temp; }
🪴 lodash
라이브러리의 cloneDeep
함수 사용
lodash
는 JavaScript에서 널리 사용되는 유틸리티 라이브러리입니다. lodash
의 cloneDeep
함수를 사용하면 객체의 깊은 복사를 매우 쉽게 할 수 있습니다. 이 방법은 성능도 좋고 다양한 데이터 타입과 복잡한 객체 구조에도 잘 작동합니다.javascriptCopy code import cloneDeep from 'lodash/cloneDeep'; const deepCopiedList = cloneDeep(list.groupList);
🌳 structuredClone
함수 사용
structuredClone
는 HTML Living Standard에 추가된 API로, 깊은 복사를 지원합니다. 이 함수는 다양한 내장 타입, 포함하여 순환 참조와 같은 복잡한 데이터 구조를 복사할 수 있습니다.
structuredClone
는 모던 브라우저에서 지원됩니다.javascriptCopy code const deepCopiedList = structuredClone(list.groupList);
방법 선택 기준
- 재귀 함수 사용: 커스텀 로직이 필요하거나 특정 타입의 객체를 다르게 처리해야 할 때 유용합니다.
lodash
의cloneDeep
: 대규모 프로젝트나 복잡한 데이터 구조를 다룰 때, 이미lodash
를 사용하고 있다면 편리합니다.
structuredClone
: 최신 브라우저에서 사용 가능하며, 다양한 타입과 복잡한 구조를 갖는 데이터를 복사해야 할 때 매우 유용합니다. 하지만 구형 브라우저에서는 지원되지 않을 수 있습니다.
참조
🔗
🔗