배워서 남 주자

[React] setState는 비동기 함수일까?

미래에서 온 개발자 2022. 12. 7. 16:18

상태 변경 함수(setState)를 써서 상태 갱신을 할 때 데이터 확인을 위해 아래 예시 코드의 6번 라인과 같이 console.log(state)를 해보면 다음과 같이 실제 입력값보다 한 개씩 밀려서 출력이 되는 걸 알 수 있다.

 

const [username, setUsername] = useState("");


 const handleChangeUser = (event) => {
    setUsername(event.target.value);
    console.log(username);
 };
 
 return (
 // ...
         <input
          type="text"
          value={username}
          className="tweetForm__input--username"
          placeholder="your username here.."
          onChange={handleChangeUser}
        ></input>
 // ...
 )

 

이는 setState 함수가 이벤트 핸들러 내에서 비동기적으로 작동하기 때문이다. 

 

이게 무슨 뜻일까? 위의 문장을 이해하려면 React가 상태 업데이트를 실시간으로 처리하는 것이 아니라 일괄적으로 모아서 처리(batch)하는 방식을 알아야 한다.

 

배칭(batching)이란 React가 더 나은 성능을 위해 여러 개의 상태 업데이트를 하나의 리렌더링으로 묶는 것을 말한다. 

 

setState() 함수 자체는 동기적인 코드이다. 상태 변경 함수를 이벤트 핸들러 밖에서 호출해 보면 동기적으로 작동한다. 하지만 리액트 v.17부터는 이벤트 핸들러useEffect에서 배치 기능을 지원한다. 변경된 값들을 모아서 한 번에 업데이트를 진행하여 불필요한 렌더링을 줄이고자 배치 기능을 사용해 비동기로 작동하는 것이다. 

 

위의 예제 코드로 돌아가보면 handleChangeUser 이벤트 핸들러 함수 내부에서 상태변경함수가 호출될 때마다 렌더링을 하는 것이 아니라 이벤트 핸들러 함수가 끝나고 한꺼번에 렌더링을 하기 때문에(이것이 바로 배칭이다!) 실제 input 창에 입력하는 것보다 한 박자씩 밀려서 콘솔에 찍히는 것이다. console.log(state)를 이벤트 핸들러 함수 밖으로 빼보면 input 창에 입력하는 문자가 동시에 콘솔에 출력된다. 

 

그런데 문제는 React가 그동안 상태 업데이트에 대한 배칭을 언제할 것인지 일관적이지 못했다는 점이다. 예를 들어 useEffect 내부에서 Ajax 요청을 통해 데이터를 외부 소스로부터 가져와서 상태를 업데이트한다면 React는 업데이트를 배칭하지 않고, 상태 변경 함수를 호출한 횟수만큼 독립적인 업데이트를 수행하였다. 다시 말해 React는 클릭과 같은 브라우저 이벤트의 업데이트만을 배칭했고, fetch API를 쓰는 경우 상태 업데이트가 이벤트가 진행되는 중이 아닌, 이벤트가 완료된 후의 콜백에서 실행되기 때문에 배칭이 적용되지 않았다. 

 

그러나 React v.18의 createRoot를 통해 모든 업데이트들은 어디서 왔는가와 무관하게 자동으로 배칭된다. 이것이 자동 배칭(automatic batching)이다. v.17에서는 Promise, setTimeout 등에서 배칭이 되지 않았던 반면, v.18의 자동 배칭 이후로는 동일하게 상태 업데이트를 배칭한다.  

 

그렇다고 해서 React 18이라고 능사는 아니고 createRoot를 사용해야 외부 이벤트 핸들러에서도 배칭을 해준다. 레거시 render를 사용하면 이전 방식을 유지한다. 

 

 

📍 배칭을 하는 이유 : 렌더링 최소화를 통한 성능 최적화 

리액트에서는 상태 값이 변경되면 리렌더링이 발생한다. 상태 변경이 하나라면 리렌더링이 한 번만 발생하지만 동시에 수십, 수백 개의 값이 변경된다면? 리액트가 이때마다 동기적으로 렌더링을 하게 된다면? 속도가 얼마나 느려질지 생각해 보라. 

(배칭 주기가 16ms라는 블로그 포스팅들이 많은데 공식 문서 등 정확한 출처에서는 확인하지 못했다.) 

 

 

📚 참고자료: 

 

컴포넌트 State – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

React 18: 렌더링 최적화를 위한 자동 배칭

Introduction

immigration9.github.io

 

useState는 동기 비동기? (동기적 처리)

며칠전 기술 면접을 보러가서 **`useState`는 동기일까요 비동기일까요? 비동기라면 동기적 처리를 하기 위해서는 어떤 방법을 써야 할까요?** 라는 질문을 받았다. 이 질문을 받기 전까지는 그냥

velog.io