불변성 in js (CallStack? MemoryHeap? 얕아? 깊어?)

LOCA·2023년 10월 31일
0

자바스크립트

목록 보기
9/10
post-thumbnail

객체나 배열의 변수인 State를 setState를 이용하여 변경했음에도 react가 감지하지 못하여 렌더링이 일어나지 않는 경험이 있을 것이다.
왜일까 ? javascript의 변수 저장방식과 리액트에서 왜 불변성을 지켜야하는지 알아보자.

1. Javascript의 메모리 구조


자바스크립트 엔진은 3 가지의 메모리 공간을 가진다

  1. Code Area : 실행한 자바스크립트 코드를 담는다.
  2. Call Stack : 지역 변수를 저장한다. 변수들은 LIFO(후입선출) 형식 으로 저장된다.원시 타입들이 이곳에 저장된다.
  3. Memory Heap : 참조타입들이 할당되는 곳이다. 콜스택과 달리 Heap의 메모리 할당은 랜덤하게 배치된다.

원시타입: Boolean, String, Number, null, undefined, Symbol -불변해!

참조타입: Object, Array - 가변해!

위 두가지 타입별로 데이터 저장방식과 할당 방식이 달라진다 !!


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

let a = 10 
let b = [1,2,3]
let c = {name:"카레유", job:"개발자"}
let d = function(^_^){함수~~~~}


원시 타입은 콜스택의 Value에 변수값이 저장된다.

참조타입은 메모리 힙의 주소가 콜스택의 Value에 저장된다.
참조타입 변수의 메모리힙의 Value에 저장된다.


3. 원시타입과 참조 타입의 변수 할당과 재할당

원시타입의 특징


원시 타입의 변수는 변수값을 변경해! 라는 명령을 받았을때,
기존 콜스택의 값을 변경하지 않고! 새로운 주소를 추가해 값을 저장하고
변수가 그 주소를 바라보게 한다.
기존 콜스택의 메모리 영역의 value를 변경하지 않는다.
메모리 영역에는 아무런 변동이 없다 !!
이것을 "불변성"😯 이라고 한다.

  • 재할당 후 값10은 가비지콜렉터의 대상이된다. (적절한시점에 메모리에서 해제됨)

참조 타입의 특징


변수 값을 할당하고, push메서드를 이용해 새로운 값을 추가하였다.
원시타입처럼 "불변성" 지켜질까?
실제로는 변수 a와 변수b가 바라보고 있는

콜스택의 값이 변경되지 않고,
메모리 힙에 있는 데이터가 변경이 되어 불변성😯이 유지가 되지 않았다 !


여기서 잠깐 정리 ! 😯

원시타입( Boolean, String, Number, null, undefined, Symbol)의 변수들은 콜스택에 값이 저장되고 , value가 변경되면 콜스택에 변경된 값을 가지는 새로운 메모리 영역을 생성한다.

참조타입(Object, Array)의 변수의 값은 메모리 힙에 저장되고 콜스택에는 value 값이 저장된 메모리 힙의 주소가 저장된다. 따라서 변수의 값이 변경되면 메모리 힙의 값은 변경되지만 콜스택 값은 변경이 없다.


4. 얕은비교 vs 깊은비교

위와 관련된 개념인 얕은비교와 깊은비교에 대해 알아보자.

obj1과 obj2가 각각 같은 값으로 선언이 되어있다.
콜스택 영역의 값을 비교하는 것을 "얕은 비교" 라고 하고,
메모리힙 영역의 값을 비교하는 것을 "깊은 비교" 라고 한다.



이를 콘솔로그로 확인하여보면 obj1과 obj2는 얕은 비교를 수행했을때,
각각 다른 콜스택의 주소이기 때문에 false가 찍히게 되고,
JSON.stringify 메서드를 활용해 깊은 비교를 수행하면 이값은 true를 반환하는 것을 볼 수있다.


5. 왜 불변성 지켜야해 ? In REACT

1) 리액트의 state 변화 감지 기준은 "콜스택의 주소값"이다.

그것은 바로 리액트의 상태 업데이트의 원리에 숨겨져있다.
우리가 setState를 할때

리액트는 콜스택의 주소값만을 비교하여 상태 변화를 감지한다.이를 "얕은 비교"라고 한다.

계산리소스를 줄여주기 때문이에.
그말인 즉슨 콜스택의 값만 빠르게 비교하기 때문에 계산 리소스를 줄여줄수 있다는 것이다.

따라서 콜스택의 값이 이전과 다르다면 재렌더링을 해서 리액트는 상태를 업데이트 한다.

리액트의 빠른 state 변화 감지를 할 수 있도록 해주는 장점이자, 불변성을 지켜야하는 이유이다.


위 버튼을 눌러서 새로운 number(원시타입)의 값을 계속 할당을 해주면 콜스택의 값이 바뀌기 때문에 그걸 알아서 감지를 해 화면에 재렌더링이 잘 일어난다.

원시타입의 변화의 메모리 영역값이 변경하지 않는, 불변성을 유지한채로 새로운 메모리 영역에서 변경된 값이 저장 되기 때문에 콜스택 의 주소값의 변화가 감지된다 -> 재렌더링!

하지만,

참조타입에 setState할때 push를 통해 배열을 수정을 아무리 해도
콜스택의 값은 변경이 되지 않았기때문에 감지를 하지 못하고 재렌더링이 일어나지 않는다.

참조 타입은 콜스택에 메모리 힙의 주소만을 저장하고, 값은 메모리 힙에 저장,변경되기 때믄에 참조 타입의 값을 변경하면 콜스택의 주소값은 변경이 없어 react는 state의 변경이 없다고 감지하기 때문에 변경된 state는 재랜더링되지 않는다.
따라서 참조타입의 변경된 값을 react가 감지 할 수 있도록 불변성을 유지해야한다

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

불변성을 지키는 것은 기존의 메모리 영역에 변경을 가하지 않는 것으로
외부에 존재하는 원본 데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용한다는 것을 의미한다. 이는 기존 메모리 영역의 값이 변경할 경우에 기존 메모리 영역의 값을 사용하는 다른 코드에서 발생할 수 있는 오류를 사전에 방지 할 수 있으며 , 예기치 못한 오류를 해결할 코드를 추가적으로 만들지 않아도 된다는 이점도 있다.


6. 참조타입의 불변성을 지키는 방법 in React

새로운 배열을 반환하는 매서드들을 적극 활용하면 된다!


기존 배열을 변경하는 메소드를 사용해야 하는경우에
splice,push등 원본 데이터를 변경하는 메소드를 사용해야 한다면 , 기존의 배열을 복사한 새로운 객체를 만들고, 복사한 객체의 데이터를 변경한 후에 이걸 state에 넣어주는 것도 생각 해 볼 수 있다. ex) spread operator

ES2023에서는 기존의 reverse나 sort가 원본 배열을 변경하는 단점을 보완한 새로운 배열을 반환하는 메서드들 toReserved,toSorted도나왔다!

참고 :
https://velog.io/@badahertz52/%EC%B0%B8%EC%A1%B0%ED%83%80%EC%9E%85%EA%B3%BC-React%EC%9D%98-%EB%B6%88%EB%B3%80%EC%84%B1
https://www.youtube.com/watch?v=LRlooA5sYhU&t=301s

불변성이 뭔가요 ?

리액트에서 어떻게 불변성을 지키고 계신가요 ?

profile
helloWorld

0개의 댓글