[ React.js ] 객체 복사, 진짜 '복사'일까?
객체 복사, 진짜 '복사'일까?
React에서 발생한 참조 이슈와 해결 방법
React로 작업을 하다 보면 종종 원본 데이터와 수정된 데이터를 따로 관리하고 싶을 때가 생긴다.
예를 들어, 사용자 정보 폼을 띄우고 수정했다가 취소 버튼을 누르면 처음 상태로 되돌려야 하는 경우다.
그래서 보통 다음과 같이 두 개의 상태를 선언해 놓고 사용한다.
const [originalData, setOriginalData] = useState({}); const [editData, setEditData] = useState({});
처음 데이터를 API로 받아오면 originalData와 editData를 동일하게 초기화한다.
setOriginalData(res.data); setEditData(res.data);
그리고 수정 버튼을 누르면 editData를 바꿔 나간다.
문제는 여기서 발생했다.
수정했더니 원본도 바뀐다?
수정 로직을 추가하고, 수정 중인 값을 setEditData로 변경해보면…
이상하게도 originalData도 같이 바뀌어버리는 현상이 발생했다.
왜 그럴까?
원인: 객체는 '복사'가 아니라 '참조'된다
JavaScript에서 객체나 배열 같은 참조형 데이터는
다른 변수에 대입하더라도 **복사되는 게 아니라 '같은 주소를 참조'**하게 된다.
즉, 아래처럼 데이터를 세팅하면:
이건 editData와 originalData가 같은 메모리 주소를 바라보게 되는 것이다.
따라서 editData를 수정하면 originalData도 같이 바뀌는 건 너무 당연한 결과였다.
해결 방법: '깊은 복사(Deep Copy)'를 해주자
단순한 = 할당으로는 안 되고, 아예 새로운 객체로 만들어줘야 한다.
여기엔 여러 방법이 있다.
방법 1. JSON.parse(JSON.stringify(obj))
const deepCopy = JSON.parse(JSON.stringify(obj));
가장 많이 알려진 방식 중 하나
단점: 속도 느림, 함수/undefined/Symbol 등은 복사되지 않음
실제로 사용해봤는데 작동은 잘 되었지만, 데이터 양이 많을 경우 약간 느려지는 감이 있었다.
방법 2. Lodash의 cloneDeep
https://lodash.com/docs/4.17.15#cloneDeep
import cloneDeep from 'lodash/cloneDeep'; const deepCopy = cloneDeep(obj);
- Lodash에서 제공하는 깊은 복사 함수
- 속도나 안정성 모두 준수
- 함수, 배열, 중첩 객체 등 대부분의 복사 시나리오에 대응 가능
현재는 이 방법을 사용 중이고, 큰 문제 없이 잘 작동하고 있다.
마무리
처음엔 단순히 복사했다고 생각했던 코드가,
알고 보면 객체를 같이 바라보고 있었던 것이었다.
이런 참조 이슈는 JS에서 흔하게 발생할 수 있고,
특히 상태 관리할 때 놓치면 예상 못 한 데이터 변경이 일어날 수 있다.
무조건 cloneDeep이 정답은 아니지만,
복사 범위가 깊거나 구조가 복잡한 데이터일수록 한 번쯤 사용하는 걸 고려해볼 만하다.