
useState란?
useState는 컴포넌트에서 상태(State)를 관리하기 위한 React Hook이다.
여기서 **상태(State)***란 시간이 지남에 따라 변경될 수 있으며, 화면(UI)에 영향을 주는 데이터를 의미한다.
예를 들어 다음과 같은 값들이 상태가 될 수 있다.
- 사용자가 입력한 값
- API로부터 받아온 데이터
- 모달 열림/닫힘 여부
- 다크 모드 여부
- 장바구니 상품 목록
이처럼 값이 변경되면 화면도 함께 변경되어야 하는 데이터는 useState로 관리한다.
기본 구조
const [state, setState] = useState(initialValue);
각 요소의 역할은 다음과 같다.
- state (상태): 현재 상태 값을 저장하는 변수
- setState (업데이트 함수): 상태를 변경하는 함수(Setter)
- initialValue (초기값): 상태의 초기값. 컴포넌트가 처음 렌더링될 때 한 번만 사용된다.
예를 들어 숫자를 관리하는 경우 다음과 같이 사용할 수 있다.
const [count, setCount] = useState(0);
- count : 현재 숫자
- setCount() : 숫자를 변경하는 함수
- 0 : 초기값
일반 변수(let)를 사용하면 안 될까?
일반 변수와 useState의 가장 큰 차이점은 React가 값의 변경을 인지할 수 있는지 여부이다.
- 일반 변수: 값이 바뀌어도 리액트는 그 사실을 모른다.
- useState: setState를 호출하면 React에게 상태가 변경되었다는 사실을 알린다.
배칭, 상태 스냅샷
배칭(Batching)
여러 개의 상태 업데이트를 하나의 렌더링으로 묶어서 처리하는 React의 최적화 기능이다.
예를 들어 하나의 이벤트 핸들러에서 setState()를 여러 번 호출하더라도 React는 이를 모아서 한 번만 렌더링한다.
이 덕분에 불필요한 렌더링이 줄어들어 성능이 향상된다.
상태 스냅샷(State Snapshot)
React의 상태는 실시간으로 변경되는 값이 아니라, 현재 렌더링 시점의 값을 기준으로 동작한다.
즉, 이벤트 핸들러는 자신이 생성된 렌더링 당시의 상태를 기억하고 있으며, 이를 상태 스냅샷(State Snapshot) 이라고 한다.
다음 코드를 살펴보자.
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};
return (
<button onClick={handleClick}>
{count}
</button>
);
}
겉으로 보면 count가 3 증가할 것 같지만, 실제 결과는 1이다.
그 이유는 세 번의 setCount가 모두 동일한 상태 스냅샷을 참조하기 때문이다.
현재 렌더링 시점의 count가 0이라면 React는 내부적으로 다음과 같이 처리한다.
setCount(0 + 1);
setCount(0 + 1);
setCount(0 + 1);
세 번 모두 1로 변경해 달라는 요청이기 때문에 최종 결과는 1이 된다.
💡 해결책: 함수형 업데이트
이전 상태를 기반으로 새로운 상태를 계산해야 하는 경우에는 함수형 업데이트를 사용하는 것이 좋다.
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 결과는 3
이전 상태를 기반으로 새로운 상태를 계산해야 하는 경우에는 함수형 업데이트를 사용하는 것이 좋다.
5. 불변성 유지 (Immutability)
React에서는 상태를 변경할 때 기존 객체나 배열을 직접 수정하지 않고 새로운 객체나 배열을 만들어야 한다.
이를 불변성(Immutability)을*유지한다고 한다.
React는 상태가 변경되었는지 판단할 때 이전 값과 새로운 값의 참조(Reference) 를 비교한다.
따라서 기존 객체나 배열을 직접 수정하면 React가 변경을 제대로 감지하지 못하거나 예상하지 못한 동작이 발생할 수 있다.
① 직접 변경 금지
const [user, setUser] = useState({ name: 'Kim', age: 20 });
// 기존 객체를 직접 수정 금지
user.name = 'Lee';
② 참조 변경 - 전개 연산자 사용 (Good Practice)
기존 객체를 복사한 뒤 변경할 값만 덮어써 새로운 객체를 생성해야 한다.
- 객체 수정
-
setUser({ ...user, // 기존 데이터 복사 name: 'Lee' // 특정 값만 덮어쓰기 });
- 중첩 객체
-
const [user, setUser] = useState({ name: 'Kim', profile: { age: 20, city: 'Seoul' } }); // ❌ profile 객체의 참조값은 그대로라 리액트가 변화를 모를 수 있음 setUser({ ...user, profile: { city: 'Busan' } }); // age가 사라짐! // ✅ 깊은 곳까지 새 객체로 만들어줘야 함 setUser({ ...user, profile: { ...user.profile, city: 'Busan' } });
- 배열 수정
-
const [items, setItems] = useState([1, 2, 3]); // 요소 추가 setItems([...items, 4]); // 요소 삭제 (filter 활용) setItems(items.filter(item => item !== 2)); // 요소 수정 (map 활용) setItems(items.map(item => item === 1 ? 100 : item));
정리
- useState는 컴포넌트의 상태를 관리하는 React Hook이다.
- 상태가 변경되면 React는 새로운 렌더링을 수행한다.
- 여러 번의 setState()는 배칭되어 한 번만 렌더링된다.
- 상태는 현재 렌더링 시점의 값을 기억하는 상태 스냅샷을 기반으로 동작한다.
- 이전 상태를 이용해 값을 변경할 때는 함수형 업데이트를 사용하는 것이 좋다.
- 객체와 배열 상태는 직접 수정하지 말고 새로운 객체나 배열을 생성하여 불변성을 유지해야 한다.
'React' 카테고리의 다른 글
| useEffect (0) | 2026.06.10 |
|---|---|
| React 컴포넌트 구조 및 설계 (0) | 2026.06.10 |
| React 탄생 (0) | 2026.06.09 |
| React 렌더링(간단버전) (0) | 2026.04.13 |