비동기 작업과 setState의 조화

722025년 10월 26일4

<CommentList> 컴포넌트의 성공은 팀에 중요한 교훈을 주었다. 바로 리액트가 비동기(Asynchronous) 작업을 얼마나 우아하게 다루는가에 대한 것이었다.

과거의 jQuery나 백본 코드에서 비동기 작업은 언제나 골칫거리였다. API 요청을 보내고, 그 응답이 언제 올지 모르는 상태에서 콜백 함수가 실행될 때, 종종 UI의 상태가 꼬이는 일이 발생했다.

예를 들어, 사용자가 댓글 목록을 보고 있는 사이에 다른 페이지로 이동했다고 가정해 보자.

  • 기존 방식에서는, 페이지는 이동했지만 백그라운드에서는 여전히 댓글 API의 응답이 날아올 수 있었다.
  • 뒤늦게 도착한 응답을 처리하는 콜백 함수는, 이미 사라지고 없는 DOM 요소를 찾으려고 하다가 에러를 뿜거나, 의도치 않은 동작을 유발했다.
  • 개발자는 이런 엣지 케이스를 막기 위해, 콜백 함수 안에서 “혹시 지금 내가 그리려는 DOM 요소가 아직 화면에 존재하니?” 같은 방어적인 코드를 일일이 추가해야 했다.

하지만 리액트에서는 이 문제가 놀랍도록 간단하게 해결되었다.

“리액트는 setState를 호출하는 시점에, 해당 컴포넌트가 여전히 화면에 존재하는지(마운트된 상태인지)를 자동으로 확인합니다.”

조던이 팀원들에게 리액트의 내부 동작을 설명했다.
“만약 사용자가 다른 페이지로 이동하여 <CommentList> 컴포넌트가 이미 언마운트(unmounted)된 상태라면, 뒤늦게 도착한 API 응답 콜백이 this.setState를 호출하더라도 리액트는 이 호출을 그냥 무시해 버립니다. 이미 사라진 컴포넌트의 상태를 업데이트하려는 무의미한 시도이니까요.”

이 한 가지 사실만으로도 개발자들은 수많은 방어적인 코드를 작성해야 하는 고통에서 해방될 수 있었다. setState는 그 자체로 안전장치 역할을 했다.

또한, setState의 또 다른 중요한 특징이 드러났다. 바로 ‘일괄 처리(Batching)’ 기능이었다.

데이빗이 한 가지 시나리오를 제시했다.
“만약 아주 짧은 시간 안에 여러 개의 setState 호출이 연속적으로 일어난다면 어떻게 됩니까? 예를 들어, 데이터를 받아온 직후 isLoadingcomments 상태를 따로따로 업데이트하는 거죠.”

// 안티 패턴 (비효율적인 사용법)
.then(function(response) {
  this.setState({ isLoading: false }); // 첫 번째 호출
  this.setState({ comments: response.data }); // 두 번째 호출
}.bind(this));

“이러면 리액트가 render를 두 번 호출하고, 화면을 두 번 깜빡이며 그리게 되나요?”

“아닙니다.” 조던이 대답했다. “리액트는 충분히 영리합니다. 리액트는 이벤트 핸들러나 API 콜백 같은 특정 컨텍스트 내에서 발생하는 여러 setState 호출을 하나로 묶어서 처리합니다.”

그는 리액트의 내부 작업 큐(Queue)를 설명했다.

  1. 첫 번째 this.setState({ isLoading: false }) 호출이 들어오면, 리액트는 이 변경 요청을 큐에 넣고, 즉시 리렌더링을 실행하지 않는다.
  2. 이어서 두 번째 this.setState({ comments: response.data }) 호출이 들어오면, 이 요청 역시 큐에 추가한다.
  3. 현재 실행 중인 코드 블록(콜백 함수)이 모두 끝나면, 리액트는 큐에 쌓인 모든 상태 변경 요청을 한 번에 병합하여 최종적인 다음 상태(next state)를 계산한다.
  4. 그리고 이 최종 상태를 기반으로, render를 단 한 번만 호출하여 화면을 업데이트한다.

이 일괄 처리(Batching) 매커니즘은 불필요한 리렌더링을 막아주는 강력한 성능 최적화 기능이었다. 개발자는 성능을 걱정하지 않고 필요한 상태 변경을 논리적으로 나눠서 호출할 수 있었다.

비동기 작업과 setState의 아름다운 조화.
안전한 상태 업데이트와 지능적인 일괄 처리.
리액트는 비동기 로직이 야기하는 수많은 혼란과 복잡성을, setState라는 단 하나의 잘 설계된 API 뒤에 완벽하게 감춰버렸다. 개발자는 이제 비동기 코드의 타이밍 문제나 엣지 케이스를 걱정하는 대신, 오직 ‘어떤 상태로 바꿀 것인가’에만 집중하면 되었다.