리액트를 공부하다 보면 이런 코드를 한 번쯤은 써보게 된다
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
"3번 증가시키려고 했는데... 결과는 1만 올라가네?"
이유가 뭘까?
리액트에서 setCount()는 즉시 상태를 바꾸지 않고,
"업데이트 예약"만 해두는 함수다.
그래서 위 코드를 실행하면, 아래처럼 실행됨
| 실행 시점 | count 값 | 실행 결과 |
|---|---|---|
| 처음 렌더 | 0 | setCount(0 + 1) → 1 |
| 두 번째 | 0 | setCount(0 + 1) → 1 |
| 세 번째 | 0 | setCount(0 + 1) → 1 |
→ 결국 setCount(1)이 3번 쌓이고, 마지막 하나만 적용됨
→ 최종 count = 1
리액트는 성능 최적화를 위해,
하나의 이벤트 안에서 발생한 여러 개의 setState 호출을 한 번에 처리한다.
이걸 "배치 처리(Batching)"라고 부른다.
"어? 같은 이벤트 안에서 상태를 여러 번 바꾼다고?
렌더링은 한 번만 해도 되겠네!"
setCount(count + 1) 대신
setCount(prev => prev + 1)를 쓰면 해결됨
function handleClick() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}
이제 리액트는 업데이트 함수 각각을 순서대로 적용해 준다:
| 단계 | prev 값 | 결과 |
|---|---|---|
| 1 | 0 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
→ 최종 count = 3
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
// setCount(count + 1); // ❌ 결과: 1
// 아래처럼 고치자
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}
return (
<>
<h1>{count}</h1>
<button onClick={handleClick}>+3</button>
</>
);
}
| 패턴 | 설명 | 결과 |
|---|---|---|
setCount(count + 1) | 렌더 시점의 count만 참조 → 같은 값 3번 요청됨 | 1 |
setCount(prev => prev + 1) | 이전 값을 기준으로 순서대로 업데이트 | 3 |
prev => prev + 1처럼 함수형 업데이트를 사용하자.setState가 바로 상태를 바꾸는 게 아니라 예약만 하기 때문이다.