리액트의 불변성

리액트의 불변성

·

3 min read

React 를 사용하면서 state 를 변경 했지만, 리액트가 제대로 감지하지 못하여 변화가 일어나지 않아 자료를 찾아보다가

리액트는 불변성을 지켜야 한다.

state를 직접 변경하게 되면 value 값은 바뀌지만 참조 값은 바뀌지 않으므로 렌더링이 일어나지 않는다.

위와 같은 말을 많이 접하게 되었습니다. 리액트를 처음 배울 때 몇 번 들어봤던 말인데 그 당시 제대로 잡고 갔어야하는데 다시 같은 실수를 방지하고자 공부하여 정리 합니다.

자바스크립트 메모리 구조

img

Call Stack(콜 스택)

  • 실행 중인 함수를 추적하여 계산을 수행하고, 지역 변수를 저장 (LIFO 방식)

  • 고정된 크기로 메모리에 저장(실제 데이터가 변수에 할당)

  • 원시 타입이 이곳에 저장 됩니다. ( Boolean, String, Number, Null, Undefined, Symbol )

Memory Heap(메모리 힙)

  • 콜 스택과 달리 메모리 할당은 랜덤하게 배치 → 데이터의 크기가 정해지지 않고 메모리에 저장 + 변수에 Heap 메모리 주소 값 할당

  • 메모리 누수를 방지하기 위해 JS 엔진의 메모리 관리자가 항상 관리

  • 참조 타입이 이곳에 할당 됩니다. ( Object, Array )

앞서 먼저 설명한 이유는, 타입별로 데이터 저장방식과 할당 방식이 다르기 때문 입니다.

원시 타입 과 참조 타입의 데이터 저장 방식

1. 원시 타입

한 번 생성된 원시 값은 읽기 전용 값으로, 변경 할 수 없습니다.

carbon (9)

  • age 변수의 값을 31로 재할당 했으나, oldage의 값이 바뀌지 않는 이유는 원시 타입의 값은 변경할 수 없는 값이기 때문 입니다.

  • 값을 변경할 수 없기 때문에 age = 31 이라는 새로운 값을 재할당할 때는, 새로운 메모리공간을 확보해 그 곳에 값을 저장 합니다.

또 다른 예시로

carbon (10)

  • 변수 string은 'data1' 였고, 여기에 'data2'를 재할당하였는데 기존 메모리 영역 1에 있는 'data1'의 값은 그대로 두고 메모리 영역2에 'data2'를 새로 할당 했습니다.

  • 즉, 메모리영역에서 'data2'는 'data1'을 대체하는 것이 아니라 새로운 영역에 할당됩니다. (불변성)

이번에는 참조 타입에 대해 살펴보겠습니다.

2. 참조 타입

img (1)

만약 위의 그림과 같이 기존의 변수를 변경하는 경우를 살펴보면, 변수 값이 변경되면 콜스택의 변화는 없으며 메모리 힙의 value 값만 변경 됩니다. 즉, 새로운 영역에 할당되지 않고 기존의 메모리 영역의 값이 변경되므로 불변성 유지가 되지 않습니다.

참조 타입의 불변성을 지키기 위해서는, spread operator, map, filter, slice ,reducer 등등 새로운 배열을 반환하는 메소드들을 활용 합니다.

예시를 보면

carbon

array.push(5)는 원본데이터를 수정함으로써 불변성을 지켜주지 않았습니다.

반면, array = [1, 2, 3, 4] 는 새로운 배열 [1, 2, 3, 4]을 할당하고 새로운 참조 값을 만들어주어 불변성을 지켜준 것이 됩니다.

위와 같은 특징이 불변성 입니다.

불변성 : 값이나 상태를 변경 할 수 없는 것

또 다른 정의는 '메모리 영역에서 한 주소에 대한 값을 변경 할 수 없는 것' 입니다.

✅ 정리! React 에서 불변성을 지켜야 하는 이유

1. 리액트에서 상태 업데이트를 하는 원리 때문입니다

리액트는 상태 값 업데이트를 하는 경우 '얕은 비교' 를 합니다.

얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고 객체의 참조 주소 값만 변경되었는지 확인합니다.

  • 즉, 배열이나 객체의 속성을 비교하는게 아니라 이전 참조 값과 현재 참조 값만을 비교하여 상태 변화를 감지합니다.

원시 타입의 경우 값을 변경하면 참조한 주소가 바뀌어 쉽게 상태 변경을 감지할 수 있습니다.

참조 타입의 경우 일부 프로퍼티만 수정할 경우 상태 변화가 감지되지 않기 때문에 참조 주소 값을 새로 반환해야 합니다.(깊은 복사)

2. 불변성을 지킴으로서 사이드 이펙트와 복잡한 코드를 방지 할 수 있습니다.

기존 메모리 영역의 값을 사용하는 다른 코드에서 발생할 수 있는 오류를 사전에 방지 합니다.

  • 복사본을 만들어서 사용하기 때문 입니다.

👋 추가! 얕은 복사 vs 깊은 복사

얕은 복사 (Shallow Copy)

참조 타입인 경우 객체가 가리키는 값들의 묶음을 가리키는 주소 값만 복사

  • 얕은 복사로 데이터를 복사 후 복사본에서 프로퍼티 값을 바꾸면,

  • 복사본과 원본은 모두 같은 객체를 바라보고 있는 상태에서 프로퍼티가 변경되는 것이므로 원본의 값도 변하게 됩니다.

  • 불변성 유지 ❌

원시 타입인 경우 값이 담긴 데이터 공간의 주소가 변수 공간에 바로 저장되어 있기 때문에, 값을 복사한 후 복사본의 값을 바꾸면 복사본이 새로운 값의 주소를 저장하게 되므로 원본은 변하지 않습니다.

  • 원시 타입 데이터는 얕은 복사와 깊은 복사의 결과가 동일

깊은 복사 (Deep Copy)

값 자체를 복사하여 다른 메모리 주소를 할당

  • 이 경우 원본과 복사본이 서로 다른 객체를 가리키게 되므로 복사본의 프로퍼티가 변경되어도 원본이 변경되지 않습니다.

참고

https://hsp0418.tistory.com/171

https://friedegg556.tistory.com/m/87

https://ko.javascript.info/object-copy

https://babycoder05.tistory.com/entry/React-Virtual-DOM-%EA%B3%BC-%EB%B9%84%EA%B5%90-%EC%9B%90%EB%A6%AC%EC%99%80-%EC%96%95%EC%9D%80-%EB%B9%84%EA%B5%90?category=1023016