[React] setState 는 비동기 함수가 아니다.

kimminjunnn·2025년 6월 4일

CS

목록 보기
29/34

React를 공부하다 보면 자주 듣게 되는 말이 있다.
바로 "setState는 비동기다!"

그런데 정말 그럴까?
이 질문에 제대로 답하기 위해서는 setState의 동작 원리를 정확히 이해할 필요가 있다.
아래에서 하나하나 살펴보자.


setState란?

setState는 React에서 컴포넌트의 상태(state)를 변경할 수 있도록 도와주는 함수다.
상태가 변경되면 해당 컴포넌트는 자동으로 리렌더링되며, UI가 새 상태에 맞게 갱신된다.


그런데 setState는 동기일까? 비동기일까?

함수 자체는 동기이다

console.log("1");
setState(100);
console.log("2");

위 코드를 실행해보면 "1"이 출력된 직후, 지연 없이 "2"가 출력된다.

setState가 호출되었음에도 다음 줄의 코드 실행이 멈추지 않기 때문에,
setState 함수 자체는 동기적으로 실행되는 함수임을 알 수 있다.


하지만 상태는 즉시 반영되지 않는다.

setState(count + 1);
console.log(count); // 이전 값이 출력됨
  • 이유는 setState가 상태를 바로 바꾸는 것이 아니라,
    React 내부의 업데이트 큐(queue) 에 변경 요청을 등록하기 때문이다.
  • 실제 상태 변경과 리렌더링은 나중에 일괄적으로 처리(batch) 된다.

React의 상태 처리 흐름

1. setState 호출
2. 변경 요청이 큐에 저장됨
3. React가 한꺼번에 처리(batch update)
4. 컴포넌트를 리렌더링
  • 이처럼 setState실행은 동기, 하지만 효과는 비동기적으로 나타남

React는 왜 이렇게 동작하게 만들었을까?

React는 성능 최적화를 위해 여러 개의 상태 변경을 한 번의 렌더링으로 처리한다.
이를 Batching(배치 처리) 이라고 하며, 렌더링 비용을 최소화할 수 있는 매우 중요한 최적화 전략이다.


상태 값에 의존할 때는 주의해야 한다

setState(count + 1);
setState(count + 1); // 결과적으로 +1만 반영됨!
  • 위 코드처럼 setState를 연달아 호출해도 둘 다 같은 이전 count 값을 참조하게 된다.
  • 그 결과 두 번 호출했지만 실제로는 한 번만 증가한다.

해결책: 함수형 업데이트

React는 이런 문제를 막기 위해 함수형 업데이트(functional update) 방식을 제공한다.

setCount(prev => prev + 1);
setCount(prev => prev + 1);
  • 여기서 prev는 함수 인자로 들어오는 가장 최신 상태값이다.
  • 이 함수는 React가 상태 업데이트를 실행하는 "그 순간"에 호출된다.
  • 따라서 첫 번째 업데이트가 반영된 이후의 최신 값이 두 번째에도 정확히 전달된다.

prev는 키워드인가요?

아니요!
prev단순한 매개변수(parameter) 이름일 뿐이다.
다음 코드도 완전히 동일하게 동작한다:

setCount(before => before + 1);
setCount(anyName => anyName + 1);
  • 중요한 것은 함수 자체이며,
  • React가 이 함수에 가장 최신 상태값을 인자로 전달해 준다.

클로저(Closure)의 역할

  • 이 함수(prev => prev + 1)는 setState가 실행될 때까지 "저장된 함수" 상태로 남아 있음
  • 이때 React는 내부적으로 해당 함수를 호출하며 최신 상태값을 전달
  • 이 구조가 바로 클로저이다:
    상태를 변경하기 위한 로직을 함수 형태로 저장해 두었다가, 나중에 실행하는 것

왜 안전한가?

  • 상태 업데이트가 비동기적이기 때문에,
    외부 변수에 의존하는 방식(setCount(count + 1))은 타이밍 문제로 오류가 생길 수 있다.
  • 반면, 함수형 업데이트는 "최신 값"을 기준으로 실행되므로 항상 정확하다.

React 18부터는 Batching이 더 강력해졌다

이전에는 이벤트 핸들러 내부에서만 setState가 배치 처리됐지만,
React 18부터는 Promise, setTimeout, async/await 등 모든 비동기 함수 안에서도 자동으로 batching이 적용된다.

useEffect(() => {
  setTimeout(() => {
    setCount(c => c + 1);
    setCount(c => c + 1);
  }, 1000);
}, []);
항목React 17React 18
결과 값count = 1count = 2
Batching 적용 여부❌ (setTimeout 내에서는 안 됨)✅ (모든 비동기 컨텍스트에서 적용)
리렌더링 횟수2번1번
상태 누적❌ (덮어쓰기 발생)✅ (정확하게 누적됨)

최종 정리

  • setState동기 함수지만, 상태는 비동기적으로 반영된다.
  • 이는 React가 렌더링 성능을 최적화하기 위해 의도한 설계이다.
  • 따라서 상태 값에 의존하는 경우에는 클로저 기반의 함수형 업데이트를 사용하는 것이 안전하다.

면접 답변 예시

"setState는 자바스크립트 상으로는 동기 함수이지만, 상태 반영은 비동기적으로 이루어집니다. 이는 React가 여러 상태 업데이트를 한 번에 처리하는 batching 구조 때문이며, 이로 인해 성능 최적화가 가능합니다. 상태값에 의존할 때는 클로저 기반의 함수형 업데이트를 통해 정확한 상태 변경을 보장할 수 있습니다."

profile
Frontend Engineers

0개의 댓글