본문 바로가기
React

[React] 리액트와 불변성

by LasBe 2023. 1. 5.
반응형

⚡️ 시작하기 전


자바스크립트나 리액트를 학습할 때 불변성에 대한 이야기는 꾸준하게 등장합니다.

값이나 상태는 불변성을 항상 유지해야한다거나… 리액트에서 불변성이란 특성 때문에 객체의 프로퍼티만 수정하면 변경이 감지가 안된다거나…

뭔지 알고싶지 않아도 어느순간 기본적인 곳에 계속해서 문제가 생겨 부딪힐 수 밖에 없습니다.

그럼 자바스크립트에서 불변성이 어떠한 원리로 작동 하는지 알아보겠습니다.

⚡️ 자바스크립트의 메모리 구조


자바스크립트의 메모리 구조는 콜 스택(Call Stack)메모리 힙(Memory Heap)으로 구성되어 있습니다.

여기에 저장되는 값은 타입에 따라 다른 방식으로 저장됩니다.

자바스크립트에는 다음과 같이 두가지의 타입이 존재합니다.

  1. 원시 타입: Boolean, String, Number, null, undefined, Symbol
  1. 참조 타입: Object, Array, Function

원시 타입은 위 사진의 변수 a와 같이 콜 스택에 값이 바로 저장되지만, 참조 타입의 경우 변수 b, c, d 와 같이 메모리 힙에 실제 값이 저장되고 그 주소를 콜 스택에 저장합니다.

⚡️ 값의 재할당


📌 원시 타입

원시 타입 값인 변수 ab가 생성되어 콜 스택에 저장되었습니다.

변수 a의 값을 기존에 저장된 값인 20으로 재할당 했습니다.

그러면 콜 스택에 저장된 변수 a의 값을 바꾸는 것이 아니라 변수 a가 가르키는 주소를 기존에 저장되어있는 값의 주소로 교체합니다.

즉, 이는 실행 컨텍스트의 렉시컬 환경에 있는 변수 a주소를 교체하는 것입니다.

그럼 없는 값을 변수에 재할당하면 어떻게 될까요?

위 그림과 같이 콜 스택에 새로운 주소와 값을 저장하고 변수 b가 가르키는 주소만 교체합니다.

💡
이를 통해 자바스크립트의 불변성이란 메모리 영역에서 한 주소에 대한 값은 변경될 수 없음을 의미합니다.

📌 참조 타입

자바스크립트는 함수 또한 객체로 취급되어 객체와 배열의 저장 방식을 따릅니다.

그러나 이 저장방식은 원시 타입과 조금은 다르게 작동합니다.

참조 타입인 객체 변수와 배열 변수를 만들어 값을 변경 했는데 메모리 힙 영역의 값만 변경되고 변수가 가르키는 콜 스택의 주소는 바뀌지 않은 것을 확인할 수 있습니다.

이는 자바스크립트에서 재할당할 수 없는 상수를 선언하는 키워드인 const로 생성한 객체나 배열의 프로퍼티를 왜 수정할 수 있는지도 알 수 있는 부분이며, 이는 불변성이 유지되지 않는 문제를 야기시켜 리액트에서 여러가지 골치아픈 문제를 일으킵니다.

⚡️ 리액트와 불변성


리액트는 기본적으로 상태(값)가 변경되면 이를 감지하고 렌더링을 합니다.

리액트에서는 효율적으로 상태 변경을 감지하기 위해 얕은 비교를 사용해 상태가 참조하고 있는 주소만을 비교해 렌더링 여부를 결정합니다.

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

const [person, setPerson] = useState({
  name: "jang",
  age: 26,
});

return (
  <div>
    <span>{person.name}</span>
    <span>{person.age}</span>
    <button
      onClick={() => {
        person.age = 27;
        setPerson(person);
        console.log(person.age);
        // 콘솔에는 27이 찍히지만 같은 주소를 참조해 리렌더링은 일어나지 않는다.
      }}
      >
      Change Age
    </button>
  </div>
);

그러나 참조 타입은 일부 프로퍼티만 수정할 경우 일관성이 유지되지 않아 상태 변경이 감지되지 않기 때문에 원하는 타이밍에 렌더링이 일어나지 않는 상황이 발생합니다.

그럼 참조 타입을 수정했을 때 콜 스택의 주소 값을 바꾸는, 즉 불변성을 지키려면 어떻게 해야할까요?

const [person, setPerson] = useState({
  name: "jang",
  age: 26,
});

return (
  <div>
    <span>{person.name}</span>
    <span>{person.age}</span>
    <button
      onClick={() => {
        setPerson((prev) => {
        // 객체 자체를 바꾸어주어 불변성 유지
        return { ...prev, age: 27 };
        });
      }}
    >
      Change Age
    </button>
  </div>
);

정답은 참조 타입의 값을 통째로 갈아주어 새로운 주소를 참조하는 것입니다.

setPerson({ name: ‘jang’, age: 27 })

이와 같이 직접 값을 갈아치워 불변성을 유지할 수 있으나

이전 값을 스프레드 연산자를 이용해 객체를 생성하는 것이 항상 최신 상태가 되도록 보장하기 때문에 권장합니다.

useState로 간단하게 상태의 불변성을 유지하는 방법을 알아보았습니다.

리액트를 관통하는 주제인 불변성은 프로젝트의 규모가 커질 때 최적화 하는 Memoization hook인 useCallbackuseMemo를 완벽하게 이해하고 사용하는데 알아두어야 하는 개념이기 때문에 반드시 숙지하셔야 합니다!

반응형

댓글


오픈 채팅