이번 글에서는 React로 ToDo List 프로젝트를 만들면서
state management의 개념과
배열 / 객체 형태의 state를 왜 새로 만들어서 업데이트하는지에 대해 정리해보려고 한다.
나머지 개발 내용은 readme에 적어놓음
React에서 state management란,
컴포넌트가 가지고 있는 상태(state)를 어떻게 관리하고, 변경하고, 다른 컴포넌트에 전달할 것인가에 대한 전략이다.
React의 모든 화면 업데이트는 결국 state가 바뀌면서 발생한다.
state 변경 → React 감지 → 컴포넌트 리렌더링
즉,
state를 어떻게 다루느냐가 React 앱의 구조와 안정성을 결정한다.
ToDo List 프로젝트를 만들면서 가장 많이 쓰인 state는 다음과 같다.
const [todo, setTodo] = useState([]);
여기서
삭제, 수정, 체크 토글, 검색 등 모든 기능은
결국 todo 배열을 변경하는 작업이다.
그런데 이때 중요한 규칙이 있다.
state를 직접 수정하지 말고, 반드시 새로운 state를 만들어서 업데이트해야 한다.
todos.push(newTodo);
setTodos(todos);
이렇게 하면 React는 state가 바뀌었다는 걸 감지하지 못할 수 있다.
왜일까?
React는 객체나 배열의 내용이 아니라,
참조값(메모리 주소)가 바뀌었는지로 state 변경을 판단한다.
prevState === nextState
이 비교에서 주소가 같으면:
"state 안 바뀜"
→ 리렌더링 안 함
const user = { name: "Kim", age: 20 };
// ❌ 같은 객체 수정
user.age = 21;
setUser(user);
// ✅ 새로운 객체 생성
setUser({
...user,
age: 21 });
두 번째 방식만 React가 변경을 감지한다.
내가 질문했던 내용:
"그럼 spread로 전체 state 가져와서 바꾸고 싶은 것만 바꾸는 거잖아?"
✅ 정답이다.
setTodos(
todos.map(todo =>
todo.id === id
? { ...todo, isDone: !todo.isDone }
: todo
)
);
이 방식의 핵심은:
이 프로젝트에서는 다음과 같은 방식으로 state 관리했다.
useState로 로컬 상태 관리useRef로 고유 ID 관리모든 기능은:
state 수정 → 새 객체 생성 → setState → 리렌더링
이 흐름을 기반으로 동작한다.
useState만 사용했지만,
을 충분히 느낄 수 있었다.
을 학습해 보고 싶다.