React 상태 변화 감지 원리: 메모리 주소와 불변성

일어나 개발해야지·2025년 12월 30일

React

목록 보기
6/9

Intro

상태 자동 업데이트는 React를 사용하는 주된 이유 중 하나이다. DOM 자동 업데이트를 위해 나온 라이브러리가 React인 만큼 대부분의 경우 개발자가 신경쓰지 않아도 된다.

하지만 React가 모든 데이터의 변경을 상태 변화로 감지하는 것은 아니다. set함수를 실행했음에도 리렌더링이 발생하지 않거나 반대로 값이 동일해보이는데 리렌더링이 발생하는 경우도 있다. React가 어떤 기준으로 상태를 감지하는지 알면 리렌더링 동작을 수월하게 예측이 가능하다.

이번 포스팅에서는 React가 어떤 기준으로 상태 변화를 감지하는지, React에서 반복적으로 강조되는 불변성과도 연결해보면 좋을 것 같다

1.리액트는 상태를 어떻게 감지할까

  • React는 상태 변경을 감지할 때, 이전 state와 현재 state의 값을 하나하나 비교하지 않는다.
  • 데이터의 메모리 주소가 달라졌는지를 확인한다.
  • 그리고 메모리 주소가 달라지면 리렌더링한다.

    메모리 주소의 변경 여부는 데이터 타입과 저장 방식에 따라 달라진다.

1-1. 데이터 타입과 저장 방식 (복습)

Javascirpt 데이터 타입은 기본형과 참조형으로 구분된다.
이전 포스팅(JavaScript 데이터 타입과 복사 방식)의 내용을 돌아보자면, 데이터 타입에 따라 값의 저장 방식과 복사방식이 달라진다고 이야기했다.

기본형(Primitive) 데이터

  • 종류 : number, string, boolean, null, undefined, symbol(ES 2015), bigint(ES 2020)
  • 복사 방식 : 값 복사(항상 독립적)

참조형 데이터

  • 종류 : object, array, function, date, 정규표현식 (RegExp), Map, Set 등
  • 복사 방식 : 깊은 복사(독립된 인스턴스), 얕은 복사 (프로퍼티 값의 타입에 따라 결과가 달라짐)

2.상태 변경을 감지하지 못하는 경우

state에서 다루는 값이 원시형이거나, 참조형이지만 깊은 복사를 사용하는 경우 항상 독립된 인스턴스가 생성되므로 메모리 주소가 변경된다. 따라서 setState가 호출될때마다 리렌더링이 발생한다.

하지만 참조형에서 얕은 복사를 사용하는 경우, 중첩 데이터는 기존 참조를 유지한다.
React는 메모리 주소가 변경되지 않은 데이터에 대해서는 상태를 감지하지 못하므로 리렌더링이 발생하지 않는다.
“최종적으로 state가 가리키는 참조가 바뀌었는가”로 설명된다.

2-1.얕은 복사

  • 최상위 객체만 새로운 참조를 생성한다.
  • 내부에 중첩된 객체나 배열을 기존 참조를 그대로 유지한다.
  • 이로 인해, React가 상태 변경을 감지하지 못한다.

🤔 얕은 복사에 {}의 역할은 ?

Q: 복사 과정을 살펴보면 {} 새로운 객체를 만든 뒤, 원본객체를 할당하는 방식이다.
그렇다면 얕은 복사에서의 {}는 어떤 역할을 하는건가 ?

A: {}는 최상위 객체에 새로운 메모리 주소를 부여하기 위한 껍데기이다.
이 껍데기 덕분에 React는 “state가 바뀌었다”고 판단할 수 있다.
다만, 그 안에 들어 있는 중첩 객체들은 여전히 이전 메모리 주소를 공유한다.

🤔 배열은 왜 중첩된 객체처럼 동작하나

Javascript 에서 배열은 Array 타입을 가진 객체이다
배열의 내부 구조를 살펴보면 중첩객체로 이뤄져있기때문에 중첩객체와 동일하게 동작한다.


typeof [] // "object"
Array.isArray([]) // true

//내부 구조
{
  0: { id: 1 },
  1: { id: 2 },
  length: 2
}

2-2.리렌더링이 발생하는 경우

defaultData와 updateData의 값은 같아 보이지만,
새로운 객체 리터럴을 생성했기 때문에 React 입장에서는 서로 다른 상태로 인식된다.


const defaultData = {
  name: 'John',
}
const updateData = {
    name: 'John', // 값은 동일
 }

const [user, setUser] = useState(defaultData);

setUser(updateData)

console.log(defaultData === updateData) //  false ✅ 리렌더링 발생

2-3. 리렌더링이 발생하지 않는 경우

값이 달라졌는데 내부적으로는 이전 메모리 주소를 유지하고 있는 상황


const defaultData = {
  name: 'John',
  address: {
    city: 'Seoul',
  },
};

const updateData = { ...defaultData };
updateData.address.city = 'Busan'; // 내부 객체 직접 수정

const [user, setUser] = useState(defaultData);

setUser(updateData);

console.log(defaultData === updateData); // false (최상위는 다름)
console.log(defaultData.address === updateData.address); // true (중첩 객체는 같은 주소!) 
// ❌ 리렌더링 발생하지 않음

3.React에서 불변성이 강조되는 이유

3-1. 가변성이 중요한거 아닌가..?

Q: React에서는 메모리주소가 바뀌어야 상태가 변경된다는 점에서는 가변성이 중요한거 아닌가 ..?
A: 한번 저장된 state 객체를 직접 수정하지 않는다는 의미이다.

기존 값을 변경하지 않고, 새로운 객체를 만들어 교체하는 방식

3-2. 데이터 관점에서의 불변성

React는 데이터가 어떻게 바뀌었는지는 직접 비교하지 않는다.
오로지 state가 가리키는 메모리 주소가 바뀌었는만 확인한다.

  • 불변성(데이터 유지) → 새로운 데이터가 필요하면 새 데이터 생성(메모리 주소 변경)
  • 가변성(데이터 변경) → 새로운 데이터가 필요하면 기존 값을 변경(메모리 주소 유지)

3-3. 개발자가 해야 할 일

React는 state의 값의 변경 여부를 일일이 비교하지 않는다.
대신 이전 state와 현재 state가 같은 참조인지만 확인한다.

원본 객체를 직접 수정하지 말고, React가 상태를 감지할 수 있도록 의도적으로 새로운 참조를 만들어줘야한다.

React에서 불변성은 상태를 업데이트 하기위한 전제 조건이다

0개의 댓글