React를 공부할 때에 불변성을 지켜야한다는 설명을 자주 들었다. 그렇다면 왜 불변성을 지켜야할까? 그 왜에 대해서 공부해보자!
변수와 불변성
js 메모리 구조
JS의 3가지 메모리 공간
1️⃣ CODE AREA
- 실행한 js 코드를 담는 공간
2️⃣CALL STACK
- 실행 중인 함수를 추적하며 계산을 수행하고 변수를 저장
- 변수들은 LIFO 형식으로 저장됨
- 원시 타입들이 저장되는 공간
3️⃣HEAP
- 참조 타입들이 할당되는 공간
- 콜 스택과 달리, Haep의 메모리 할당은 랜덤하게 배치됨
- 메모리 누수를 방지하기 위해 JS 엔진의 메모리 관리자가 항상 관리함
원시타입
Boolean, String, Number, null, undefined, Symbol
참조타입
Object, Array
원시타입과 참조타입의 데이터 저장방식과 재할당 비교
javascript의 원시타입과 참조타입의 데이터 저장 방식
콜스택
- 원시타입은 변수 값이 콜 스택의 value에 저장
- 참조타입은 메모리 힙의 주소가 콜 스택의 value에 저장
메모리 힙
- 참조타입 변수의 값은 메모리 힙의 value에 저장
변수 할당과 재할당
원시타입
- 원시타입은 기존에 저장된 값을 재할당하면
콜스택에 저장된 변수 a의 value를 바꾸는 것이 아니라 a가 가리키는 주소를 기존에 저장되어 있는 값의 주소로 교체한다.
⏩ 실행 컨텍스트의 렉시컬 환경에 있는 변수 a의 주소를 교체하는 것!
- 원시타입은 없는 값을 변수에 재할당하면
콜 스택에 새로운 주소와 값을 저장하고 변수 b가 가리키는 주소만 교체한다.
이를 통해 자바스크립트의 불변성이란
메모리 영역에서 한 주소에 대한 값은 변경될 수 없음을 의미함을 알 수 있다.
참조타입
- 참조타입은 값은 변경하면
메모리 힙 영역의 값만 변경되고 변수가 가리키는 콜 스택의 주소는 바뀌지 않는다.
불변성
불변성의 의미는 메모리 영역에서 값을 변경할 수 없다는 의미이다.
불변성을 지켜야 하는 이유
1️⃣ 리액트의 state 변화 감지 기준은 "콜 스택의 주소값"이다.
리액트는 콜 스택의 주소값만을 비교하여 상태 변화를 감지한다. (이를 "얕은 비교"라고 함)
원시타입의 변화는 불변성(메모리 영역 값이 변경되지 않음)을 유지한채로 새로운 메모리 영역에 변경된 값이 저장되고 콜 스택의 주소 값이 바뀌면서 변화가 감지된다.
그러나 참조타입은 콜 스택에 메모리 힙의 주소만을 저장하고, 값은 메모리 힙에서 저장•변경된다.
따라서 참조타입의 값을 변경하면 콜 스택의 주소 값은 변경이 없기 때문에 리액트는 state의 변경이 없다고 감지하기 때문에 변경된 state는 리렌더링되지 않는다.
참조타입의 변경된 값을 리액트가 감지할 수 있도록 불변성을 유지해야한다.
2️⃣ 불변성을 지킴으로서 사이드 이펙트와 복잡한 코드를 방지할 수 있다.
불변성을 지키는 것은 기존의 메모리 영역에 변경을 가하지 않는다는 것이다.
외부에 존재하는 원본 데이터를 직접 수정하지 않고, 원본 데이터의 복사본을 만들어서 값을 사용하는 것을 의미한다. 이는 기존 메모리 영역의 값을 변경할 경우에 기존 메모리 영역의 값을 사용하고 있는 다른 코드에서 오류가 발생할 수 있는 것을 사전에 방지할 수 있다. 그리고 예기치 못한 오류를 해결할 코드를 추가적으로 만들지 않아도 된다는 이점도 있다.
참조타입의 불변성을 지키는 방법
그렇다면 참조 타입을 수정했을 때 콜 스택의 주소값을 바꾸는, 즉 불변성을 지키려면 어떻게 해야할까?
위에서 설명했듯이 리액트는 기본적으로 상태(값)가 변경되면 이를 감지하고 렌더링을 한다.
리액트에서는 효율적으로 상태 변경을 감지하기 위해 얕은 비교를 사용해 상태가 참조하고 있는 주소만을 비교해 렌더링 여부를 결정한다.
원시 타입의 경우 값을 변경하면 주소가 바뀌어 쉽게 상태 변경을 감지할 수 있지만
참조 타입의 경우 일부 프로퍼티만 수정할 경우 일관성이 유지되지 않아 상태 변경이 감지되지 않기 때문에 원하는 타이밍에 렌더링이 일어나지 않는 상황이 발생하게 된다.
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인 useCallback과 useMemo를 완벽하게 이해하고 사용해야하는데 이를 반드시 숙지해야한다!
참고자료
[React] 리액트와 불변성
⚡️ 시작하기 전자바스크립트나 리액트를 학습할 때 불변성에 대한 이야기는 꾸준하게 등장합니다.값이나 상태는 불변성을 항상 유지해야한다거나… 리액트에서 불변성이란 특성 때문에 객
lasbe.tistory.com
React의 불변성
React에서 불변성은 왜 지켜져야하며 어떻게 지켜야 할까에 대해 정리했습니다.
velog.io
'💻Client > react' 카테고리의 다른 글
[React] Ref란? (2) | 2023.08.29 |
---|---|
[React] 가상돔(Virtual DOM)이란? (4) | 2023.08.28 |
[React] React란? (feat.왜 React를 사용할까?) (0) | 2023.08.26 |