참조값 다루기
React에서 배열과 객체 상태를 다룰 때 왜 직접 변경하면 안 되는지 이해합니다.
참조값은 다르게 다뤄야 합니다
이전 챕터에서 숫자 하나를 state로 다루는 건 해봤습니다.
그런데 실무에서는 숫자보다 배열이나 객체를 state로 다루는 경우가 훨씬 많습니다.
여기서 한 가지 주의할 게 있습니다.
배열이나 객체는 숫자처럼 다루면 화면이 안 바뀝니다.
왜 그런지, React가 상태를 비교하는 방식부터 봅시다.
React는 상태를 어떻게 비교할까
setState를 호출하면 React는 이전 값과 새 값을 비교합니다.
비교 결과가 "다르다" 면 화면을 다시 그리고, "같다" 면 무시합니다.
React는 === 로 비교합니다. 자바의 == 연산자와 같은 감각입니다.
원시값과 참조값의 차이
원시값(숫자, 문자열)은 값 자체를 비교합니다.
setState(0) // 이전 값
setState(1) // 새 값 → 0 !== 1 → 다르다 → 화면 갱신참조값(배열, 객체)은 **참조(주소)**를 비교합니다.
const items = ['사과', '바나나'];
items.push('포도');
setState(items); // 같은 배열 참조 → 이전과 같다 → 무시배열 안에 '포도'가 추가됐지만, React 입장에서는 같은 배열 객체를 다시 받은 것입니다.
내용이 바뀌었는지는 보지 않습니다. 참조가 같으면 같은 겁니다.
자바에서 List 두 개를 == 로 비교하면 주소만 보는 것과 똑같습니다.
나쁜 예시
할 일 목록에 항목을 추가하는 코드입니다.
const [todos, setTodos] = useState(['출근', '점심']);
function addTodo() {
todos.push('퇴근'); // 기존 배열을 직접 변경
setTodos(todos); // 같은 참조 → React는 무시
}push는 기존 배열을 직접 바꿉니다.
배열의 내용은 바뀌었지만 참조는 그대로이기 때문에, React는 "달라진 게 없네"하고 넘어갑니다.
화면은 안 바뀝니다.
객체도 마찬가지입니다.
const [user, setUser] = useState({ name: '김개발', age: 30 });
function birthday() {
user.age = 31; // 기존 객체를 직접 변경
setUser(user); // 같은 참조 → React는 무시
}좋은 예시
새 배열을 만들어서 넘기면 됩니다.
스프레드 연산자(...)를 쓰면 기존 내용을 복사하면서 새 참조를 만들 수 있습니다.
const [todos, setTodos] = useState(['출근', '점심']);
function addTodo() {
setTodos([...todos, '퇴근']); // 새 배열 → 다른 참조 → 화면 갱신
}...은 스프레드 연산자입니다. 기존 배열을 펼쳐서 새 배열을 만듭니다.
객체도 같은 방식입니다.
const [user, setUser] = useState({ name: '김개발', age: 30 });
function birthday() {
setUser({ ...user, age: 31 }); // 새 객체 → 다른 참조 → 화면 갱신
}패턴 정리
자주 쓰는 패턴을 모아두면 이렇습니다.
| 동작 | 나쁜 예시 | 좋은 예시 |
|---|---|---|
| 배열에 추가 | items.push(x) | [...items, x] |
| 배열에서 제거 | items.splice(i, 1) | items.filter((_, idx) => idx !== i) |
| 배열 항목 수정 | items[i] = x | items.map((v, idx) => idx === i ? x : v) |
| 객체 필드 수정 | obj.key = x | { ...obj, key: x } |
왼쪽은 전부 기존 참조를 직접 건드리는 방식이고, 오른쪽은 새 참조를 만드는 방식입니다.
React에서는 오른쪽만 써야 합니다.
정리
- React는 상태를
===로 비교한다. 참조가 같으면 변경으로 보지 않는다. - 배열이나 객체를 직접 바꾸면 참조가 안 바뀌기 때문에 화면도 안 바뀐다.
- 항상 새 배열, 새 객체를 만들어서 넘겨야 한다.
- 스프레드 연산자(
...)가 가장 기본적인 도구다.
직접 해보기
- 배열 state에 항목을 추가하고 삭제하는 컴포넌트 만들어보기
다음 챕터 예고
상태를 다루는 감각이 잡혔으니, 다음에는 화면이 그려진 다음에 실행되는 코드를 어떻게 다루는지 알아보겠습니다.