학습 목표: 객체와 배열을 State로 쓸 때의 함정을 피하고, 불변성을 지키며 안전하게 업데이트함
(1) "참조형"이란?
| 구분 | 예시 | 특징 |
| 원시형 | 숫자, 문자열, 불린 | 값 자체를 비교 |
| 참조형 | 객체, 배열 | 메모리 주소를 비교 |
// 원시형 — 값이 같으면 같은 것
5 === 5 // true
'hi' === 'hi' // true
// 참조형 — 모양이 같아도 '다른 것'!
{ a: 1 } === { a: 1 } // false 😱
[1, 2] === [1, 2] // false 😱
왜 false? 참조형은 "같은 메모리 주소를 가리키는가"로 비교함
모양은 똑같아도 새로 만든 객체는 다른 주소라 false임
React랑 무슨 상관?
React는 State가 변했는지 확인할 때 ===(참조) 비교를 함
const obj = { name: '홍길동' };
obj.name = '김철수'; // 값만 바꿈
setUser(obj); // 같은 주소의 객체
// React: "어? 주소가 똑같네? 안 바뀐 거네!" 😴
// → 화면 업데이트 안 함!
(2) 불변성(Immutability) - 오늘의 절대 원칙
불변성 = 기존 값을 수정하지 않고, 항상 새로 만들기
// ❌ 기존 객체 수정(React가 못 알아챔)
user.name = '김철수';
setUser(user);
// ✅ 새 객체 생성(React가 알아챔)
setUser({ ...user, name: '김철수' });
(3) 객체 State 다루기
기본 패턴: { ...기존객체, 바꿀속성: 새값 }
function UserProfile() {
const [user, setUser] = useState({
name: '홍길동',
age: 25,
email: 'hong@example.com'
});
function updateName(newName) {
setUser({
...user, // 기존 속성 모두 복사
name: newName // name만 덮어쓰기
});
}
// ...
}
...user의 역할
setUser({ ...user, name: '김철수' });
// ↑↑↑↑↑↑
// "기존 속성을 전부 펼쳐서 복사해줘"
펼친 다음에 name: '김철수' 가 있으면 덮어쓰기가 됨
// 이렇게 풀어쓰면:
{
name: '홍길동', // ...user가 펼친 것
age: 25,
email: '...',
name: '김철수' // ← 덮어써짐!
}
// 결과: name만 바뀌고 나머진 유지! ✨
문제 해결
// ❌ 이러면 name, email이 사라짐
setUser({ age: 26 });
// ✅ 이렇게 해야 나머진 보존!
setUser({ ...user, age: 26 });
한 줄 요약: "기존 것 다 펼치고, 바꿀 것만 덮어쓰기"
(4) 배열 State 다루기 - 3대 패턴
공통 준비
const [todos, setTodos] = useState([
{ id: 1, text: '운동하기', done: false },
{ id: 2, text: '책 읽기', done: false }
]);
➕ 패턴 1: 추가 — Spread [...arr, 새항목]
function addTodo(newTodo) {
setTodos([...todos, newTodo]); // 새 배열
}
❌ 절대 하지 마세요
todos.push(newTodo); // 원본 수정! React가 모름
setTodos(todos);
➖ 패턴 2: 삭제 — filter(조건)
function deleteTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
// ↑↑↑↑↑↑
// "id가 일치하지 않는 것만 남겨줘"
}
filter는 조건에 맞는 항목들로 새 배열을 만듦. 원본을 수정하지 않아서 완벽함
✏️ 패턴 3: 수정 — map() + 조건
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, done: !todo.done } // 해당 항목만 바꾸기
: todo // 나머진 그대로
));
}
해석: "각 todo를 살펴보면서, id가 일치하면 done을 뒤집고, 아니면 그대로 두세요."
공통점: 셋 다 새 배열을 만들어 교체함. 원본은 절대 건드리지 않음
완성 예제: Todo 리스트
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '운동하기', done: false },
{ id: 2, text: '책 읽기', done: false }
]);
function toggleTodo(id) {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
}
function deleteTodo(id) {
setTodos(todos.filter(todo => todo.id !== id));
}
return (
<div>
{todos.map(todo => (
<div
key={todo.id} // index 말고 반드시 고유 id
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
{todo.text}
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</div>
))}
</div>
);
}
(5) 중첩된 객체 - 피해야 할 함정
객체 안에 객체가 있는 구조는 스프레드 지옥을 만듦
중첩이 깊어질수록 끔찍
const [settings, setSettings] = useState({
user: {
name: '홍길동',
preferences: {
theme: 'dark',
language: 'ko'
}
}
});
// theme만 바꾸려면?
setSettings{
...settings,
user: {
...settings.user,
preferences: {
...settings.user.preferences,
theme: 'light'
}
}
});
✅ 해결책 1: 구조를 평평하게
// 이렇게 나눠서 관리
const [userName, setUserName] = useState('홍길동');
const [theme, setTheme] = useState('dark');
const [language, setLanguage] = useState('ko');
// theme만 바꾸기
setTheme('light');
✅ 해결책 2: 복잡하면 Immer 같은 라이브러
setSettings(draft => {
draft.user.preferences.theme = 'light'; // 직접 수정하듯이 작성 가능
});
(6) 자주 하는 안티패턴
❌ 안티패턴 1: 배열 메서드 중 원본을 수정하는 것 사용
// 이 메서드들은 원본을 수정해요! 🚨
todos.push(newTodo); // 추가 - 원본 수정!
todos.pop(); // 삭제 - 원본 수정!
todos.splice(0, 1); // 삭제 - 원본 수정!
todos.sort(); // 정렬 - 원본 수정!
todos.reverse(); // 뒤집기 - 원본 수정!
대신 사용할 것:
- push → [...todos, new]
- sort → [...todos].sort() (복사본 정렬)
- revers → [...todos].reverse() (복사본 뒤집기)
❌ 안티패턴 2: 불완전한 객체 업데이트
// ❌ 나머지 속성이 전부 사라짐!
setUser({ age: 26 });
// ✅ spread로 기존 속성 보존
setUser({ ...user, age: 26 });
❌ 안티패턴 3: 객체/배열 직접 수정
// ❌ React가 변화 감지 못함!
user.name = '김철수';
setUser(user);
todos[0].done = true;
setTodos(todos);
이유: setter에 같은 주소의 객체/배열을 넘기면, React는 "안 바뀐 거네" 판단함
❌ 안티패턴 4: 중첩 객체로 들어가기 쉬움
// ❌ 너무 깊은 중첩
const [data, setData] = useState({
user: {
info: {
personal: {
name: '홍길동' // 여기 접근하려면 data.user.info.personal.name...
}
}
}
});
// ✅ 평평하게 재설계
const [userName, setUserName] = useState('홍길동');
// 또는 관심사별로 분리된 여러 useState'DEV > React' 카테고리의 다른 글
| [React 개발 기초] 10. React가 렌더링하는 방식 (0) | 2026.08.10 |
|---|---|
| [React 개발 기초] 9. 컴포넌트 재사용하기 (심화 Part) (0) | 2026.08.10 |
| [React 개발 기초] 7. State (1) | 2026.08.10 |
| [React 개발 기초] 6. children (0) | 2026.08.10 |
| [React 개발 기초] 5. Props (0) | 2026.08.10 |