[React/JS] 얉은 복사(Shallow Copy), 깊은 복사(Deep Copy) 그리고 React의 리렌더링

Kimyujin·2021년 10월 8일
4

Javascript의 데이터 타입

얉은 복사와 깊은 복사에 대해 이해하기 위해서는 먼저 데이터 타입에 대한 이해가 필요하다. 복사는 어떤 데이터 타입인지에 따라 다르게 진행되기 때문이다.
Javascript에서 사용되는 데이터들은 크게 원시타입과 객체타입으로 나뉜다.

원시 타입

  • 원시 값 = 변경 불가능한 값 (immutable value)
  • number, string, boolean, undefined, null, symbol

객체 타입

  • 객체 = 변경 가능한 값 (mutable value)
  • 객체, 함수, 배열 등 원시 값을 제외한 모든 것

변수에 값을 저장한다면?

const a = 10; // a에는 10이 담긴다.

변수에 원시 값을 담는다면 자체가 담긴다.

const a = { // a에는 주소가 담긴다.
 	name: 'yj',
  	age: 9
};

변수에 객체를 담는다면 객체 값이 아니라, 객체 값이 저장된 메모리 주소가 담긴다.

변수를 비교한다면?

const a = { 
 	name: 'yj',
  	age: 9
};
const b = { 
 	name: 'yj',
  	age: 9
};

원시 타입인 변수끼리 비교한다면, 값 자체를 비교하고,
객체 타입인 변수끼리 비교한다면, 참조 값을 비교한다.

console.log(a === b); // false: 각각 객체를 할당했기 때문에 참조 값 다름
console.log(a.name === b.name); // true: 값 자체 비교

Javascript의 복사

Javascript에서 복사의 형태를 관찰해보면, 크게 3가지가 있다.
값을 다른 변수에 다시 저장해버리는 일반 복사와, 값을 복사하기 위해 많이 사용하는 얉은 복사, 필요에 의해 사용되는 깊은 복사가 있다.

1. 일반 복사

변수(a)를 할당하는 형태의 복사를 말한다.

const a = {name: 'yj'};
const b = a;

변수(a)와 할당해서 만든 변수(b)는 같은 주소를 참조하고있다. (=같은 참조 값을 가지고있다.)
그렇기 때문에, a와 b 둘중 무엇이든 데이터를 조작하게 되면 두개 모두(a, b) 동일하게 변경된다.

얉은 복사와 깊은 복사를 쓰는 이유

이렇게 참조만 하게되는 복사는 원치않는 결과를 가져올 수 있기 때문에, 상황에 따라 얉은 복사와 깊은 복사를 사용할 줄 알아야한다.
얉은 복사와 깊은 복사는참조 값이 아닌 자체를 복사하기 위함이다.

2. 얉은 복사 (Shallow copy)

객체의 1depth까지만 복사하는 것을 말한다.

const user = {
  id: 22,
  name: 'yj',
  link: {
    github: 'http://~',
    instagram: 'http://~',
  },
};
const assignUser = Object.assign({}, user); 
const spreadUser = {...user}; 

console.log(user === assignUser) // false
conosle.log(user === spreadUser) // false
// (예상된 결과)
// 얉은 복사를 이용해 객체를 복사했기 때문에, 복사된 값들은 원본과 다르다. 
console.log(user.link === assignUser.link) // true
console.log(user.link === spreadUser.link) // true
// (예상못한 결과)
// 얉은 복사는 1depth까지만 값이 복사되기 때문에, 
// 중첩 객체인 link는 값이 복사되지 않고, 참조 값이 복사되어, 두개가 같다고 나온다.

코드 보러가기

3. 깊은 복사 (Deep copy)

객체가 복잡한 구조(2depth 이상)여도, 전부 복사하는 것을 말한다.

const JSONuser = JSON.parse(JSON.stringify(user));
const lodashUser = cloneDeep(user);
console.log(user.link === JSONuser.link); // false
console.log(user.link === lodashUser.link); // false
// (예상한 결과)
// 완벽하게 값이 복사되었다. 같은 객체를 공유하지 않는다.

React의 리렌더링

내가 겪었던 이슈

나는 프리온보딩 팀 프로젝트를 할 때, 이중 필터링을 구현하는 파트를 맡았다.
이중 필터링을 위한 코드는 문제없이 작성한 것 같았다.
기본 순 정렬(초기) -> 마감일 순 정렬 -> 기본 순 정렬
이렇게 마감일 순으로 정렬한 뒤에, 다시 기본 순으로 돌아오지 않았다.

spread문법을 이용해서 sort함수를 이용했는데, 알고보니 이 얉은 복사로 인해, sort(정렬)가 되고 난 뒤에는 state에 있던 기존 데이터가 변경되어, 기본 순 정렬을 해도 변경된 데이터가 그대로 출력되는 것이였다.

  const sortByDuedate = (todos: Itodo[]): Itodo[] => {
    return [...todos].sort(
      (a, b) =>
        date.convertToNumber(a.dueDate) - date.convertToNumber(b.dueDate),
    );
  };

여기서 todos의 데이터는 [{}, {}, {}] 이런 형태였고, 이 형태는 아까 얉은 복사에서 봤던 link 예제와 같은 것이다.

const arr = [{1: 'a'}, {2:'b'}, {3:'c'}];
console.log(arr[0] === [...arr][0]); // true
// 안에 있던 객체들은 결국 복사되지 않았다. 

JSON 메서드를 이용하는 것은 그렇게 좋지않다(?)고 설명 되어진 걸 봐서 고민했지만, 일단 마감시간이 다가와서 JSON 메서드를 이용해서 해결했었다.

  const sortByDuedate = (todos: Itodo[]): Itodo[] => {
    const newTodos = JSON.parse(JSON.stringify(todos)) as Itodo[];
    return newTodos.sort(
      (a, b) =>
        date.convertToNumber(a.dueDate) - date.convertToNumber(b.dueDate),
    );
  };

나중에 검색해보니 JSON 메서드는 대표적으로 함수는 복사할 수 없는 이슈가 있고, 그 외에도 문제점들이 좀 있다고 한다.
아마 lodash를 이용한 deep copy가 best인 것 같다.

리렌더링의 조건, 불변성

하나 더 짚고 넘어가야 할 개념이 있는데, 그것은 불변성이다.
React를 공부하다보면 불변성을 지켜야한다. 라는 말이 많이 나오는데, 사실 그 내용 자체가 이해가 쉽지 않았다.
불변성이란 값 재할당시 새로운 메모리 공간에 값을 넣고, 그 공간을 가리키게 하는 특성 이라고 한다.
즉 React에서 불변성을 지켜야 한다는 말은, 값을 조작하지 않고 새로운 공간에 데이터를 넣을 수 있도록 하는 것 이라고 볼 수 있겠다.
불변성을 지켜줘야(새로운 공간에 데이터를 넣어야), React가 virtual DOM을 이용해서 이전상태와 현재상태를 비교해서 리렌더링을 할 수 있다.


Reference

"Javascript:Shallow and Deep Copy", https://mygumi.tistory.com/322
"원시 값과 객체의 비교", [모던 자바스크립트 Deep Dive], 137p

0개의 댓글