
React State 업데이트는 두 가지 방식으로 이전 상태를 업데이트 할 수 있다.
훅 에서 보면
클로저로 줄 경우 클로저의 매개변수 prev는 누가, 언제, 왜 주입하는가?!
React를 쓰다 보면 상태 업데이트를 아래 두 방식으로 만난다.
// 방식 A
setState(nextState);
// 방식 B
setState(prev => nextState);
특히 방식 B를 보면 이런 질문이 자연스럽게 든다.
prev는 어디서 오는가?- 내가 호출하는 건
onChange뿐인데, 누가prev를 넣는가? setState의 로직을 재정의하는 건가?- 그냥
setState({ ...state })로 직접 넣으면 되는 것 아닌가?
setState에 함수를 넘기면, React가 그 함수를 직접 호출하면서 인자로 “현재 시점의 최신 state”를 넣어준다. 개발자는 로직을 재정의하는 게 아니라 “다음 state를 계산하는 방법”을 전달하는 것이다.React의 상태 업데이트 모델
React에서 상태 업데이트는 즉시 반영되는 “명령”이 아니라, React가 처리 타이밍을 제어하는 “요청(request)”에 가깝다.
이 관점을 잡으면 prev가 왜 필요한지 자연스럽게 이해된다.
핵심 문장:
- State는 즉시 변경되지 않을 수 있다.
- React는 성능을 위해 여러 업데이트를 묶어서(batch) 처리한다.
- 따라서 컴포넌트 내부의 state 변수는 “항상 최신”이 아니라 “렌더 시점의 스냅샷”일 수 있다.
방식 ①: 다음 state 값을 직접 전달
예시
const [input, setInput] = useState({...})
const onChange = (e) => {
const { name, value } = e.target;
setInput({
...input,
name: value,
});
};
의미
“이 값으로 상태를 바꿔 달라” — 계산을 개발자가 직접 한다.
특징
- 단순하고 직관적
- 이전 state에 의존하지 않는 경우에는 충분
- 하지만 여러 업데이트가 겹치면(배치/동시성) “옛 state” 기준으로 덮어쓸 위험이 생길 수 있음
방식 ②: 업데이트 함수를 전달 (Functional Update)
예시
const [input, setInput] = useState({...})
const onChange = (e) => {
const { name, value } = e.target;
setInput((prev) => ({
...prev,
[name]: value,
}));
};
의미
“다음 state를 직접 주지 않고, React가 가진 최신 state(prev)를 주면 그걸로 다음 state를 계산해 반환하겠다.”
핵심 포인트
내가 setInput에 데이터를 직접 주입하지 않았는데?..prev는 개발자가 전달하는 값이 아니다.- React가 호출 시점에 자동으로 주입하는 인자다.
그럼 prev는 누가, 언제 주입하는가?
setState에 함수가 들어오면 React 내부는 개념적으로 아래처럼 동작한다.
// React 내부 개념 모델
const currentState = state;
const nextState = updaterFunction(currentState); // 여기서 currentState가 prev로 들어감
state = nextState;
즉 개발자는 updaterFunction만 제공하고, React가 “현재 시점의 최신 state”를 prev로 넣어 호출한다.
왜 이런 구조가 필요한가?
React는 성능을 위해 여러 번의 state 업데이트를 즉시 적용하지 않고 한 번에 처리할 수 있다.
그 결과 “컴포넌트 안에서 보이는 state 변수”는 업데이트 직후에도 여전히 같은 값(스냅샷)일 수 있다.
아래를 보쟈
실제로 문제가 생기는 예
값을 직접 넣는 방식 ( 여러번 순차적으로 업뎃하구싶으~)
setAge(age + 1);
setAge(age + 1);
setAge(age + 1);
가정: age === 42
React가 배치 처리하면 큐에는 사실상 이렇게 쌓일 수 있다:
setAge(43)
setAge(43)
setAge(43)
결과: 43
함수형 업데이트
setAge(a => a + 1);
setAge(a => a + 1);
setAge(a => a + 1);
React는 각 updater를 순서대로 호출하며, 매번 최신 값을 인자로 준다:
a = 42→43a = 43→44a = 44→45
결과: 45
문법 오해: (prev) => ({ ... })는 왜 괄호를 쓰나?
아래 코드는 객체를 “즉시 반환(implicit return)”하는 화살표 함수다.
(prev) => ({
...prev,
name: value,
})
이는 아래와 완전히 동일하다.
(prev) => {
return {
...prev,
name: value,
};
}
{}는 기본적으로 “블록”으로 해석되기 때문에, 객체를 반환하려면 ({ ... })처럼 괄호로 감싸 “객체 리터럴”임을 명시해야 한다.
자주 생기는 오해 정리
| 오해 | 정답 |
|---|---|
setState 로직을 재정의한다 |
아니다. 동일한 setState에 “값” 대신 “업데이트 함수”를 전달하는 것뿐이다. |
prev를 내가 넣는다 |
아니다. React가 updater 함수를 호출할 때 최신 state를 인자로 주입한다. |
그냥 state를 직접 써도 항상 최신이다 |
아니다. 컴포넌트의 state 변수는 렌더 시점 스냅샷이며, 배치 처리로 인해 최신이 아닐 수 있다. |
언제 어떤 방식을 쓰면 좋나?
| 상황 | 추천 |
|---|---|
| 이전 state와 무관하게 “그 값”으로 설정 | setState(nextState) |
| 객체/배열 일부만 갱신(merge)하거나 이전 값에 의존 | setState(prev => ...) |
| 연속 업데이트(증가/누적/토글) 또는 동시 업데이트 안전성 필요 | setState(prev => ...) |
| 상태 전이 규칙이 복잡해짐 | useReducer |
최종 요약
setState(nextState)는 “값을 직접 지정”하는 방식setState(prev => nextState)는 “React가 가진 최신 state로 계산”하는 방식prev는 React가 updater 함수를 호출할 때 자동으로 주입- 배치/동시성 때문에 “컴포넌트의 state 변수”는 항상 최신이 아닐 수 있음
한 문장 요약:
React에서 함수형 업데이트는 상태를 “명령형으로 변경”하는 것이 아니라, React가 보장하는 최신 상태를 기준으로 “다음 상태를 계산”하게 만드는 안정장치다.
References :
useState – React
The library for web and native user interfaces
react.dev