Javascript에서 데이터를 복사할 때, 두 가지 개념이 존재합니다.
얇은 복사 (shallow copy) 와 깊은 복사(deep copy) 입니다.
복사란?
Javascript에서 다음과 같이 데이터를 복사할 수 있습니다.
이러한 경우 aaa는 원본, bbb는 복사본이 됩니다.
let aaa = '철수'
let bbb = aaa
console.log(bbb)
>> 철수
그리고 복사본의 값을 변경해서 재할당 해보면
bbb = "영희"
console.log(aaa) // 철수
console.log(bbb) // 영희
원본인 aaa의 값은 변하지 않고,
복사본인 bbb의 값만 변한 것을 확인할 수 있습니다.
하지만 위 방식은 객체, 배열에는 적용이 안됩니다. 아래 사진을 보면 대충 된 것 같지만 원본도 함께 같이 바뀌고 있기 때문입니다.

복사는 원본은 바뀌면 안되기 때문에 제대로 된 복사가 안 됐다고 할 수 있습니다.
이와 같은 상황은 Javascript의 데이터 타입 특징 때문에 생기는 문제입니다.
string, number, boolean등 자료형은 변수에 값을 할당하면 값 자체가 저장이 됩니다. 하지만 객체와 배열은 값이 저장되는 것이 아닌 주소값이 저장이 됩니다.

그렇기 때문에 복사는 주소값으로 복사가 잘 되었었지만 결국은 복사한 주소값의 값을 바꾼것이기 때문에 원본이 함께 변경된 것입니다.
그렇다면 어떻게 해야 객체를 복사하려면 어떻게 해야할까요?
결론부터 말하자면 객체 복사라는 것은 존재하지 않습니다. 원본 객체와 같은 값을 가진 객체를 새로 만드는 방법입니다.
// child3에 child2 복사
let child3 = {
name: child2.name,
age: child2.age,
school: child2.school
}
child3 // {name: '영희', age: 8, school: '다람쥐초등학교'}
// child3의 name 변경
child3.name = "훈이"
child3 // {name: '훈이', age: 8, school: '다람쥐초등학교'}
child2 // {name: '영희', age: 8, school: '다람쥐초등학교'}
위 처럼 새롭게 객체를 만들어 값을 변경하는 수 밖에 없습니다. 이러한 경우 원본의 값이 변경되지 않고, 유지되는 것을 확인할 수 있습니다.
하지만 위처럼 일일이 하나하나 값을 다 가져오면 너무 비효율적입니다. 객체에 들어있는 데이터의 양이 많아질 수록 실질적으로 불가능에 가까워집니다.
이를 대체하기 위해 사용하는 것이 스프레드 연산자입니다. (...)
const profile = {
name: "철수",
age: 12,
school: "다람쥐초등학교"
}
const profile2 = {
...profile
}
profile2 // {name: '철수', age: 12, school: '다람쥐초등학교'}
스프레드 연산자를 이용한 얕은 복사는 객체 안의 객체가 값으로 들어가 있는 경우에는 제대로 복사 되지 않습니다.
key에 대한 값도 주소 값으로 들어가 있기 때문에 발생하는 문제입니다.
이것처럼 스프레드 연산자를 이용한 복사를 얕은 복사(Shallow-Copy)라고 합니다.
얕은 복사는 depth 1의 깊이를 가진 데이터까지는 복사할 수 있지만,
depth 2 이상의 깊이를 가진 데이터는 복사하지 못합니다.
depth 2 이상의 깊이를 가진 데이터를 복사하는 방법은 아래와 같습니다
const profile = {
name: "철수",
age: 12,
school: "다람쥐초등학교"
hobby: {
hobby1: "축구",
hobby2: "농구"
}
}
const profile2 = {
...profile,
hobby: {
...profile.hobby
}
}
객체를 문자열의 형태로 바꾸고, 그 문자열을 다시 객체로 바꾸어 새로운 변수에 담아주면 됩니다.
JSON.stringify와 JSON.parse라는 메소드를 이용하면
객체/배열을 문자열로, 그리고 문자열을 객체/배열로 바꾸어 줄 수 있습니다.
위와 같은 방법으로 깊은 복사를 수행할 수 있습니다. 하지만 단점으로 번거롭고 느릴 수가 있습니다. 때문에 관련 작업을 도와주는 라이브러리들이 있으며 대표적으로 'lodash'가 있습니다.
lodash에서 제공하는 _.cloneDeep(value)을 사용하면 손쉽게 깊은 복사를 할 수 있습니다.
배열 또한 객체와 마찬가지 입니다. 객체와 같은 방식으로 복사가 가능합니다.
const aaa = [ "aaa", "bbb", "ccc" ]
const bbb = [...aaa]
const [ myindex, setMyindex ] = useState([
false,
false,
false,
false,
false,
false,
false,
false,
false,
false,
])
const onClickEdit = (e)=> {
const qqq = myindex;
qqq[Number(e.currentTarget.id)] = true;
setMyindex(qqq);
}
위와 같은 코드에서 onClick 함수를 실행해봤자 실질적인 변화는 일어나지 않습니다.
문제는 기존 myindex 배열을 직접 수정하고 setMyindex를 사용하여 동일한 객체를 다시 설정하고 있기 때문입니다. React는 배열 참조가 변경되지 않았기 때문에 상태가 변경되지 않았다고 판단하고 다시 렌더링하지 않습니다.
즉 qqq에 myindex를 그대로 붙여 넣어주기 때문에 같은 주소값을 공유하게 되어 상태가 변경되지 않았다고 판단하게됩니다.
따라서 배열의 사본을 작성하고 사본을 수정한 다음 setMyindex를 사용하여 새로운 배열로 상태를 업데이트 해야합니다.
const onClickEdit = (e) => {
// ...myindex 로 복사해야함
const qqq = [...myindex];
qqq[Number(e.currentTarget.id)] = true;
setMyindex(qqq);
const aaa = ["철수", "영희", "훈이"]
const bbb = ["맹구", "짱구"]
const ccc = [...aaa, ...bbb]
>> ["철수", "영희", "훈이", "맹구", "짱구"]
배열 안에 스프레드 연산자로, 두 배열을 얕은 복사해서 넣어주면 합쳐집니다.