본문 바로가기
Frontend/React

React의 원칙: Immutable Data Pattern

by 그냥하는거지뭐~ 2024. 4. 11.
목차
- 프롤로그
- JavaScript 복습
- React는 어떻게 state 변화를 감지하는가? 
- Immutable Data Pattern이란? 
- TypeScript + Immutable Data Pattern 
- 정리

 

프롤로그

React의 핵심에는 컴포넌트 기반 아키텍처가 있다. 이를 통해 개발자는 재사용 가능한 UI 조각을 만들어 레고처럼 조립한다. React는 선언적 UI를 통해 애플리케이션의 상태가 변경될 때 자동으로 변경사항을 감지하고, 필요한 컴포넌트만을 효율적으로 리렌더링함으로써 개발자가 명시적으로 조작할 필요가 없도록 한다. 이러한 프로세스는 불변성 원칙을 통해 최적화된다. 

 

state를 직접 변경하지 않고 setState를 사용해야 하는 이유.. react에서 변경된 값을 감지하는 방법.. 이게 다 Immutable Data Pattern과 관련된 것들이다. 궁금해 미칠 것 같으니 빨리 시작하자. 


JavaScript 복습

1. JavaScript에서 변경 불가능한 값과 변경 가능한 값 

 시작하기에 앞서 모던 자바스크립트 Deep Dive 11장, '원시 값과 객체의 비교'에서 배웠던 내용을 잠깐 복습하고 가자. JavaScript에서 원시값(숫자, 문자열, 불리언, null, undefined, 심벌)은 변경 불가능한 값(immutable value)이고, 객체 타입은 변경 가능한 값(mutable value)이다. 원시 값을 할당한 변수는 원시 값 자체를 값으로 갖는 반면, 객체를 할당한 변수에는 메모리 공간의 주소(참조 값)가 저장되어 있다. 변수는 이 참조 값을 통해 객체에 접근한다.

 

 불변성을 갖는 원시 값을 할당한 변수는 재할당 이외에 변수 값을 변경할 수 있는 방법이 없다. 하지만 객체는 다르다. 객체를 할당한 변수는 재할당 없이 객체를 직접 변경할 수 있다. 즉, 참조 값은 변경되지 않는다. 

 

2. 메모리 구조 

JavaScript 엔진의 메모리 공간은 call stack과 memory heap으로 나뉘는데, 각각에 대해 정리해보자. 

① Call Stack
- 원시 타입이 이곳에 저장된다. 
- 코드 중 발생하는 모든 실행 컨텍스트를 관리한다.
- LIFO로 동작
② Memory Heap
- 구조화되지 않은 메모리 영역
- 객체, 배열과 같은 참조형 데이터가 이곳에 저장된다.
- 데이터를 저장하기 위한 동적 메모리 영역으로, 필요에 따라 메모리 공간이 할당되고 해제된다. 
- 변수에 객체나 배열을 할당할 때, 해당 데이터의 실제 내용은 heap 내에 저장되며, 변수는 memory heap 내의 해당 데이터 위치를 가리키는 참조값을 저장한다. 

 


React는 어떻게 state 변화를 감지하는가? 

 React는 call stack의 주소값을 비교해서 변화를 감지한다. 값을 변경하게 되면 원시 값은 immutable value이므로 call stack에 새로운 주소에 값을 저장하는데, 객체 타입은 mutable value이므로 새로운 공간이 아닌 기존의 memory heap의 값을 변경한다. 즉, 주소가 변하지 않아 React는 state 변화를 감지하지 못한다.  

 

React는 왜 call stack의 주소값을 비교할까? React는 상태 변경 감지를 할 때 얕은 비교(shallow comparison)을 사용한다. 얕은 비교는 객체 내부의 객체까지 비교하지 않고, 최상위 프로퍼티 값 또는 참조가 변경되었는지만 확인하는 방법이다. 즉, call stack에 저장되는 원시 값은 직접 비교하여 변경을 감지하는데, 객체 또는 배열의 경우 참조가 변경되었는지를 확인한다. 즉, 객체 내부의 값이 변경되어도 참조가 동일하면 변경사항이 감지되지 않는다. 이는 react가 불변성을 중시하는 이유 중 하나이다. 

 

따라서 react에서 상태를 변경할 때는 immutable data pattern을 지켜야 한다. 불변성을 유지하지 않을 경우, react는 이를 변경으로 감지하지 못하고 UI가 업데이트되지 않는 대참사가 일어날 수 있다. 


Immutable Data Pattern이란? 

Immutable Data Pattern은 데이터 인스턴스가 생성된 후에는 수정될 수 없도록 하는 프로그래밍 패턴을 말한다. 즉, 데이터를 변경해야 할 경우, 원본 데이터를 직접 수정하는 대신 변경된 새 데이터의 복사본을 생성한다. 이 패턴은 데이터를 예측 가능하게 하고 관리하기 쉽도록 하며, 부작용을 줄인다. 이는 함수형 프로그래밍 패러다임과 잘 어울린다. 

- 예측 가능한 코드: 다른 부분에서 예상치 못한 변경을 걱정하지 않아도 된다. 
- 부작용 최소화: 데이터를 직접 변경하는 대신 새로운 복사본을 생성하기 때문에 부작동이 발생할 가능성이 줄어든다.
- 메모리 사용 최적화: 데이터의 복사본이 생성할 때 변경되지 않은 부분은 재사용이 가능하다. 

 

Example

const [items, setItems] = useState([{ id: 1, value: 'React' }]);

// 아래 코드는 items 배열의 내부 아이템을 변경하지만, items 배열의 참조를 변경하지 않는다. 
items[0].value = 'ReactJS';
setItems(items); // react에 의해 상태 변화로 간주되지 않음

// 불변성을 지킨 방법: 새로운 배열을 생성하고 상태를 업데이트한다.
const newItems = [...items, { id: 2, value: 'Vue' }];
setItems(newItems); // react가 드디어 상태 변화르 감지함

 

이처럼 state를 직접 변경하지 않고 불변성을 유지하며 setState를 사용하면, 새로운 객체가 생성되어 참조가 변경되고, react는 상태 변화를 감지할 수 있게 된다. 


TypeScript + Immutable Data Pattern 

TypeScript 코드 내에서 'readonly' 키워드나 Immutable 타입을 사용함으로써 해당 데이터가 변경될 수 없는 값임을 명시적으로 표현할 수 있다(가독성 업). 또한 자동 완성 기능을 통해 타입 관련 오류를 사전에 방지할 수 있다. 

 

Readonly, immutable types를 활용한 예시를 보자. 

interface Person {
  readonly name: string;
  readonly age: number;
  readonly address: Readonly<Address>;
}

interface Address {
  readonly street: string;
  readonly city: string;
}

const person: Person = {
  name: '그냥하는거지뭐~',
  age: 23,
  address: {
    street: 'Sinchonro 123123',
    city: 'Seoul'
  }
};

// 이 시점에서, person 객체와 그 내부의 address 객체는 수정할 수 없음
// 다음과 같은 코드는 TypeScript 컴파일러에 의해 오류로 표시된다
person.age = 32; // 오류: Cannot assign to 'age' because it is a read-only property.
person.address.city = 'Pangyo'; // 오류: Cannot assign to 'city' because it is a read-only property.

 


정리

state를 직접 변경하지 않고 setState를 사용해야 하는 이유

React는 shallow comparison으로 call stack의 주소값을 통해 변경 사항을 감지한다. 즉, 변경 가능한 값인 객체 타입의 값을 직접 변경하면 주소는 그대로인 상태에서 memory heap에 있는 값이 변경되므로 react는 이를 변화라고 감지하지 못한다. 따라서 react에서는 불변성 원칙을 지키면서 데이터의 복사본을 만들어 setState를 통해 값을 업데이트해야 한다. 

 


Reference

- 모던 자바스크립트 Deep Dive

- https://developer.mozilla.org/ko/docs/Glossary/Immutable

'Frontend > React' 카테고리의 다른 글

React의 최적화 전략  (0) 2024.04.11
프로젝트 5개 하고 돌아보는 React를 쓰는 이유  (3) 2024.04.04