🤔 왜 React 개발자는 불변성을 알아야 하는가?
React로 복잡한 웹 애플리케이션을 개발하고 계신가요? 많은 개발자들이
상태(State)를 업데이트할 때 예상치 못한 버그와 마주치곤 합니다.
분명히 상태를 변경했는데 화면이 갱신되지 않거나, 전혀 원치 않는 데이터까지
바뀌어 버리는 '마법 같은' 현상을 겪어보셨다면, 아마도 그 핵심에는
'불변성'에 대한 오해가 있을 가능성이 큽니다.
React 개발에서 불변성(Immutability)은 단순히 좋은 습관을 넘어,
성능 최적화와 예측 가능한 상태 관리를
위한 필수적인 기반입니다.
오늘 이 글에서는 초보자도 쉽게 이해할 수 있도록 불변성의 개념부터 시작해,
Array와 Object 상태를 안전하고 효율적으로 다루는 실용적인 방법을 상세히
안내해 드리겠습니다.
⚛️ 불변성이란 무엇인가? 기본 개념 정리
1. 불변성(Immutability)의 정의
불변성은 '변하지 않는 성질'을 의미합니다. 프로그래밍 관점에서 불변
객체(Immutable Object)란, 일단 생성된 이후에는 그 상태를
절대로 변경할 수 없는 객체를 말합니다.
반대로 가변 객체(Mutable Object)는 생성 후에도 내부 상태나 값을 변경할 수
있습니다. JavaScript에서 원시 타입(Primitive Types)은 불변하지만, Object와
Array는 기본적으로 가변(Mutable)합니다.
🔴 가변성 (Mutability) 예시
-
배열의
.push()메서드: 기존 배열 자체를 변경합니다. - 객체의 속성 직접 할당:
obj.key = newValue - 단점: 원본이 예기치 않게 변경되어 디버깅이 어렵습니다.
🟢 불변성 (Immutability) 예시
-
배열의
.map(),.filter(): 항상 새로운 배열을 반환합니다. - 객체 복사: Spread 연산자 (
...) 사용. - 장점: 원본 데이터 보호, 예측 가능한 상태 흐름.
2. React가 불변성을 요구하는 이유: 얕은 비교 (Shallow Comparison)
React는 상태(State)나 속성(Props)이 변경되었는지 확인하여 컴포넌트를
리렌더링할지 결정합니다.
이때 React는 이전 상태와 새 상태를 비교하는 과정에서 '깊은 비교(Deep
Comparison)' 대신 '얕은 비교(Shallow Comparison)'를 사용합니다.
얕은 비교는 객체나 배열의 내부 값을 하나하나 비교하는 대신, 단순히 두
객체의 메모리 주소(참조)가 같은지 다른지만 확인합니다.
| 비교 대상 | 가변 객체 (직접 수정) | 불변 객체 (새 객체 생성) |
|---|---|---|
| 메모리 주소 | 동일 (객체 자체는 그대로) | 다름 (새로운 객체가 생성됨) |
| React의 인식 | 변경 감지 못함 (리렌더링 X) | 변경 감지 (리렌더링 O) |
따라서 Object나 Array를 직접 수정하면(가변성), 메모리 주소는 그대로
유지되어 React는 상태가 변경된 것을 알아채지 못하고 리렌더링을 건너뛰게
됩니다.
불변성을 유지하여 항상 새로운 객체를 만들어야 React가
새로운 메모리 주소를 감지하고 화면을 정확히 갱신할 수 있습니다.
🛠️ Array와 Object 상태를 불변하게 관리하는 법
이제 실전입니다. JavaScript에서 Object와 Array를 안전하게 변경하는, 가장
일반적이고 효율적인 불변성 유지 방법을 소개합니다.
가변성을 유발하는 메서드(.push(), .splice(),
delete) 대신, 항상
원본을 유지하고 새로운 복사본을 반환하는 기법을 사용해야
합니다.
1. Object (객체) 상태 업데이트
객체의 특정 속성만 변경하고 싶을 때 사용합니다.
✅ 추천 방법: Spread 연산자 (...) 사용
const newState = { ...currentState, newKey:
'newValue' };
❌ 피해야 할 방법 (가변성):
currentState.newKey = 'newValue'; // 원본 객체 직접 변경
2. Array (배열) 상태 업데이트
배열은 불변성 유지가 조금 더 복잡합니다. 항목 추가, 수정, 삭제 시 각기 다른 방법을 사용합니다.
| 작업 유형 | 불변성 유지 방법 (✅) | 가변성 유발 방법 (❌) |
|---|---|---|
| 항목 추가 |
[...currentArray, newItem] (Spread 연산자)
|
currentArray.push(newItem)
|
| 항목 삭제 |
.filter() 메서드 사용
|
currentArray.splice(index, 1)
|
| 항목 수정 |
.map() 메서드 사용
|
currentArray[index] = newValue
|
만약 배열 안에 객체가 있거나(
[{id: 1, name: 'A'}]), 객체 안에
또 다른 객체가 있는 경우({user: {name: 'B'}}),가장 바깥쪽 객체/배열 뿐만 아니라 변경하고자 하는 그 내부 객체까지 복사하여 새로 만들어야 합니다. 이것을 깊은 복사의 한 형태로 수행해야 합니다.
// 예시 (Map 사용, 내부 객체까지 Spread):const newArray = currentArray.map(item => item.id === targetId ? {
...item, name: 'New Name' } : item);이 과정에서 Spread 연산자와 map/filter를 중첩하여 사용하는 것이 가장 흔하고 안전한 방법입니다.
✅ 결론: 불변성을 통한 성능과 안정성 확보
React 개발에서 불변성(Immutability)은 단순한 코드
컨벤션이 아니라, React의 리렌더링 원리와 직접적으로 연결된 핵심
기술입니다.
상태를 직접 수정하지 않고, 항상
새로운 객체(New Reference)를 생성하여 상태를
업데이트함으로써, React는 정확하고 효율적으로 변경 사항을 감지하고 뷰를
갱신할 수 있습니다.
🚀 요약 및 실천 가이드
- 1. 원리 이해: React는 얕은 비교로 상태 변경 여부를 판단합니다.
-
2. 핵심 기법: Array는
.map(),.filter(), Object는 Spread 연산자 (...)를 사용하십시오. -
3. 실수 방지:
.push(),.splice()등 원본 변경 메서드는 절대 사용하지 마십시오.
불변성의 원칙을 꾸준히 지켜나간다면, 더 이상 '왜 리렌더링이 안 되지?' 하는
고민 대신, 예측 가능하고 안정적인 상태 흐름을 구축할 수
있을 것입니다.
다음 단계로 React의 성능 최적화 기법인 React.memo나
useCallback, useMemo를 학습하실 때, 불변성이
얼마나 중요한지 다시 한번 깨닫게 될 것입니다.

댓글 쓰기