리액트가 불변성을 지키는 것이 중요한 이유 (얕은복사,깊은복사)

민범기·2022년 3월 19일
0

[불변성 과 얕은복사]

리액트에서 불변성은 필히 이해가 필요한 이슈인것 같아서 블로그에 정리한다.
불변성이란 기존의 값을 직접적으로 변경하지 않고 새로운 값을 만들어 내는것을 말한다.
쉽게 풀어서 말하자면 원본 그대로를 손상 시키지 않는다는 뜻이다.

const user = {name:"alsqjarl",age:28}
const copyUser = user; //이것은 배열의 값을 복사하는 것이 아니라 참조 되는 위치가 같아짐
user.age +=1;
/* user = { name: 'alsqjarlwkd', age: 28 } 
copyUser = { name: 'alsqjarlwkd', age: 28 } */
user === copyUser  // true

위의 코드가 있다고 했을때 user와 copyUser는 서로 같은 메모리 주소값을 가진다.
따라서 user가 가지고 있는 객체의 값이 변경되면 copyUser에서도 같은 주소를 가지고 있기때문에 값이 동일하게 변경 되는것을 알 수 있다.

따라서 리액트에서는 ... 이라는 sperad 연산자를 사용하여 복사한다.
...을 붙이게 되면 새로운 객체를 할당받고 메모리에 할당되어지는 주소도 서로 달라지게 된다.

const user = { name: 'alsqjarlwkd', age: 28 } 
const otherUser = { ...user }; 
user.name = 'Lee';
/* user = { name: 'Lee', age: 28 } 
otherUser = { name: 'alsqjarlwkd', age: 28 } */ 
user === otherUser // false 서로 다른 참조 값을 가지고 있음

따라서 user 값의 변경은 otherUser의 객체에 영향을 아예 미치지않는다(불변성 유지가 성공적)
간단하게 일상생활로 따지자면 인쇄물을 두장 뽑았는데 다른 한장의 인쇄물에 낙서를 해도 다른 인쇄물에는 영향을 주지 않는 것과 같다.

하지만 객체의 깊이(depth)가 깊어지면 여전히 문제가 생긴다. spread연산자는 얕은 복사를 하기 때문에 깊이가 깊은 객체를 다르게 만들고 싶다면 깊은 복사를 해야한다.

const user = { name: 'alsqjarlwkd', age: 28, friends: ['Park', 'Kim']}
const otherUser = { ...user }; 
user.name = 'Lee';
user.friends.push('Kang');
/* user = { name: 'Lee', age: 25, friends: ['Park', 'Kim', 'Kang'] }
otherUser = { name: 'alsqjarlwkd', age: 25, friends: ['Park', 'Kim', 'Kang'] }
*/ 
user === otherUser // false 
user.friends === otherUser.friends // true

위의 코드에서와 같이 얕은 복사를 하게 되면 하나의 객체의 depth 에서만 복사가 되는것을 확인 할 수있다. 점점 더 깊어진다면 불변성 유지에 큰 문제가 생길 것이다. 만약에 안쪽 depth 까지 복사를 하기위해서는 spread연산자를 뎁스 안에 한번 더 넣어줘야 한다.

const user = { name: 'alsqjarlwkd', age: 28, friends: ['Park', 'Kim']}
const otherUser = { ...user, friends: [...user.friends] }; 
user.name = 'Lee'; 
user.friends.push('Kang');

/* user = { name: 'Lee', age: 28, friends: ['Park', 'Kim', 'Kang'] } 
copyUser = { name: 'alsqjarlwkd', age: 28, friends: ['Park', 'Kim'] } 
*/ 
user === otherUser // false 
user.friends === otherUser.friends // false

이렇게 얕은 복사를 사용하여 메모리 참조 주소값을 서로 다르게 할 수 있지만, 객체의 구조가 복잡해 질수록 불변성 유지가 힘들어 진다.
그래서 immer 같은 라이브러리를 사용하여 불변성을 유지해 주기도 한다.

[깊은복사]

우선적으로 JSON.stringfy()를 통해서 객체를 하나의 쌩 문자열로 변환을 시켜준 후 JSON.parse()를 통해서 아예 다른 새로운 객체를 생성한다. 그렇게 되면 아예 다른 새로운 객체가 나타나게 된다.

let profile2 = JSON.parse(JSON.stringfy(profile));
//이런 형식으로 깊은 복사를 할 수 있다.
//profile 이라는 객체를 아예 새로운 다른 객체로 만들어 낼 수 있다.

보통 리액트 같은 경우에서는 깊은 복사보다는 얕은 복사를 많이 사용한다.
배열이나 객체에서 값을 복사해서 배열의 값을 서로 비교한다던가 할때 좋기 코드가 훨씬 더 가독성 좋다.

[리액트에서 불변성을 지키는 것이 왜 중요할까?]

우리는 개발자로서 유지보수가 가능하고 가독성이 좋은 코드를 작성해야한다.

불변성을 지키지 않는다면 사용할 데이터가 어디서 어떻게 바뀌어가는지 흐름을 쫓아가기 어렵고, 이는 곧 예기치 못한 side effects나 버그로 이어지게 만든다.

불변성을 지켜 명시적으로 작성된 코드는 다른 개발자가 코드를 보았을 때도 내가 모르는 어딘가에서 데이터가 변화했을거야! 라는 불필요한 의심없이 코드를 읽는 그대로 흐름을 따라가면서 이해할 수 있도록 돕는다.

따라서 불변성을 지키면서 데이터를 변화시킨다면, 예상가능하고 신뢰할 수 있는 코드가 될 수 있다.

애초에 불변성을 지켜야 한다는 것은 리액트가 만들어낸 새로운 컨셉이 아니라 불변성이라는 개념을 지켜가면서 state와 props를 이용할 수 있도록 하는 아이디어를 리액트에 녹여낸 것이다.

immutable한 값을 state나 props로 사용한다면 어떠한 일련의 이벤트를 통해 새롭게 만들어진 object가 변수에 할당 되는 것을 볼 수 있다. 이러한 새로운 값에 대한 참조는 기존의 값에서 값이 어떻게 변화하는지 추적하기가 쉽게 만들어준다.

단순한 불변성 개념 그 자체보다는 왜 불변성을 지켜가며 코드를 작성해야하는지 대해 좀 더 포커스를 두고 생각한다면 더 좋은 코드를 작성할 수 있을 것이다.

profile
프론트엔드 개발 지망생 민범기입니다^^

0개의 댓글