불변성이란?

Kyoungmoon Kim·2022년 12월 8일
0

불변성이란 무엇일까

사전적으로 불변성이란 값이나 상태를 변경할 수 없는 것을 의미합니다.
기존의 상태 값을 유지하면서 새로운 상태 값을 추가하는 것 을 의미합ㄴ디ㅏ.

js 메모리 구조

자바스크립트 엔진은 call stack과 heap memory 2가지 메모리 공간을 가지고 있습니다.

콜스택

실행 중인 함수를 추적해 계산을 수행하고 지역변수를 저장하는 공간입니다. 이곳에 원시 타입들이 저장됩니다.

힙 메모리

참조 타입들이 할당되는 곳입니다. 메모리 누수를 방지하기 위해 js 엔진의 메모리 관리자가 항상 관리하는 공간입니다.

  • 원시 타입 : Boolean, String, Number, null, undefined, Symbol

  • 참조 타입 : Object, Array

이 2가지의 타입을 꼭 구별해야 합니다! 왜냐면 이 타입별로 데이터 저장방식과 할당 방식이 달라지기 때문입니다.

원시타입과 참조 타입의 데이터 저장방식과 재할당 비교

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

  • 원시타입
  1. 변수 a에 값 10을 저장을 했을 경우 콜 스택의 변수값에 10이 그대로 저장
  2. 고정된 크기로 메모리에 저장
  3. 실제 데이터가 변수에 할당.
  • 참조타입
  1. 변수 b,c,d에 array, object를 저장할 경우 실제 값은 메모리 힙에 저장되고, 메모리의 힙의 주소가 콜 스택의 값에 저장
  2. 데이터 크기가 정해지지 않고 메모리에 저장

변수 할당과 재할당

  • 원시 타입의 특징

    원시타입의 변수는 변수값을 변경하라는 명령을 받을 경우, 기존 콜스택의 값을 변경하지 않고 새로운 주소를 추가해 값을 저장하고 변수 b가 바라보게 합니다. 이것을 저희가 궁금해 하던 불변성이라고 합니다.(메모리 영역의 값은 변경되지 않는다!)
  • 여기서 더이상 참조되지 않는 데이터는 가비지 컬렉터에 의해 적절한 시점에서 메모리가 해제됩니다.
  • 참조 타입의 특징

    a.에서 변수값을 할당하고, b.에서 push를 통해 값을 추가하게 되면 어떻게 될까요?
    원시타입처럼 불변성이 지켜질까요?
    실제로는 변수 a,b가 바라보고 있는 콜스택의 값이 변경되지 않고, 메모리 힙에 있는 데이터가 변경이 되어 불변성이 유지가 되지 않습니다!

왜 리액트에서 불변성을 지켜야할까?

불변성을 지킨다의 의미는 메모리 영역에서 값을 변경할 수 없게 한다, 라는 의미입니다.

왜냐하면, 리액트의 state 변화 감지 기준은 콜 스택의 주소값이기 때문입니다.
리액트는 콜 스택의 주소값만을 비교해 상태 변화를 감지하는데, 이를 얕은 비교라고 합니다. 이것이 리액트의 state를 빠르게 감지할 수 있는 장점이자 불변성을 유지해야 하는 이유입니다.
-> 원시타입의 경우 3-2처럼 값을 재할당할 경우 새로운 메모리가 할당되어 콜 스택의 주소 값이 감지가 됩니다.
-> 하지만 참조타입의 경우 참조타입의 값만 변경하면 실제로 콜스택의 주소값은 변경이 없어 state 감지가 되지 않아 리렌더링이 되지가 않습니다. 그래서 저희가 spread 연산자를 쓰고, immer 라이브러리를 사용해 새로운 배열과 오브젝트를 만들어 반환하는 이유가 그것입니다.

불변성을 지켜줌으로써 얻게 되는 또 다른 이점은 바로 사이드 이펙트를 방지하는 것입니다. 즉 외부에 존재하는 원본데이터를 직접 수정하지 않고, 원본데이터의 복사본을 만들어서 값을 사용하기에 예상치 못한 오류를 사전에 방지할 수 있습니다. 다시 반대로 생각해보면 외부의 값을 함부로 변경할 수 있는 것은 위험한 일입니다. 만약 다른 어떤 곳에서 원본데이터를 사용하고 있다고 하면 어플리케이션 어딘가에서 사이드 이펙트가 일어날 가능성이 있기 때문입니다. 결국 리액트는 불변성을 지킴으로 인해 효과적인 상태 업데이트와 사이드 이펙트를 방지하는 이점들을 얻고 있습니다.

  1. 효율적인 상태업데이트 (얕은 비교 수행)

얕은 비교란 객체의 프로퍼티를 하나하나 다 비교하지 않고, 객체의 참조 주소값만 변경되었는지 확인합니다. 얕은 비교는 계산 리소스를 줄여주기 때문에 리액트는 효율적으로 상태를 업데이트 할 수 있습니다.

  1. 사이드 이펙트 방지 및 프로그래밍 구조의 단순성.

원시타입은 애시당초 불변성 특징을 가지고 있지만 참조타입인 객체나 배열의 경우 값을 변경할 때 원본데이터가 변경될 여지가 있습니다. (불변성이 지켜지지 않을 수 있습니다). 이렇게 원본 데이터가 변경될 경우, 이 원본데이터를 참조하고 있는 다른 객체에서 예상치 못한 오류가 발생할 수 있습니다. 프로그래밍의 복잡도도 올라갑니다. 따라서 불변성을 지켜주면 사이드 이펙트를 방지하고 프로그래밍의 구조를 단순하게 유지할 수 있습니다.

어떻게 불변성을 지키는가?

spread operator, map, filter, slice, reduce 등등 새로운 배열을 반환하는 메소드들을 활용하면 됩니다.

  • splice는 원본데이터를 변경함

setState를 이용할 때 원시타입 경우에는 값을 바로 넣어주어도 되지만

참조타입인 경우에는 새로운 객체나 배열을 생성한 후 값을 넣어주어야 합니다.

정리

  • 불변성이란 메모리 영역의 값을 변경할 수 없는 것이다.
  • 리액트는 불변성을 지켜줌으로써 효율적인 상태업데이트를 한다.
  • 리액트는 불변성을 지켜줌으로써 사이드 이펙트를 사전 방지하고 프로그래밍의 구조를 단순하게 유지한다.
  • 불변성을 가진 원시타입과 달리 참조타입의 경우에는 의도적으로 불변성을 지켜주어야한다. 이 때 새로운 주소 값을 가진 객체를 생성하여 상태를 업데이트 해준다. spread operator, map, filter, slice, reduce 메소드들을 활용한다.

reference
https://hsp0418.tistory.com/171
https://evan-moon.github.io/2020/01/05/what-is-immutable/
https://narup.tistory.com/268

profile
프론트 개발 공부를 정리한 블로그입니다.

0개의 댓글