티스토리 뷰

반응형

react 18버전이 릴리즈 되었습니다. 변경점 중 하나인 batching 프로세스에 대해서 정리해보려고 합니다. 개발을 하시다보면 17버전 이하에서 이벤트 핸들러 안에 있는데 batching이 안되는 경험을 겪어보신 분들이 있을 것이라고 생각합니다.

 

이번 글에서는 batching에 대해서 간단히 소개하고, 17버전 이하에서 batching이 안되는 상황과 18버전에서 어떻게 변경이 되었는지 알아보도록 하겠습니다.

1. batching이란?

react에서 이벤트 핸들러안에 여러 setState를 하게 될 경우 같은 함수 스코프의 setState를 일괄적으로 처리하여 한번만 리렌더링 되도록 일괄처리(batching)를 합니다. 

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  useEffect(() => {
    console.log("render");  // Next 버튼 클릭 시 batcing으로 인해 한번만 찍힘.
  });
  
  function handleClick() {
    setCount(c => c + 1); // 아직 리렌더링 되지 않습니다.
    setFlag(f => !f); // 아직 리렌더링 되지 않습니다.
    // 마지막에 한번만 리렌더링 됩니다.(이를 batching이라 합니다.)
  }
  
  return (
    <div>
      <button onClick={handleClick}>Next</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

2. batching이 되지 않는 경우

예제: https://codesandbox.io/s/react-v17-tkh7ds?file=/src/App.js

console.log가 한번만 찍히는 프로세스는 react의 batching에 대해서 공부해 보신 분이라면 setCount와 setFlag가 한번만 리렌더링 되는 것이 익숙하실 것 입니다.

 

경험해 보셨을지 모르겠지만 이벤트 헨들러 내에 있어도 batching이 되지 않는 경우가 있습니다. 조금 더 자세하게 설명하자면 17버전에서는 react의 이벤트 핸들러 내에서만 batching프로세스가 동작하고, promise의 .then 콜백 내부, setTimeout에서는  batching이 되지 않습니다.

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    console.log("render");
  });

  function handleSetTimeoutClick() {
    setTimeout(() => {
      setCount((c) => c + 1);
      setFlag((f) => !f);
    });
    // Next (setTimeout)클릭 시 console에 의해 'render'가 두번 찍힘.
  }

  function handlePromiseClick() {
    const promise = Promise.resolve();
    promise.then(() => {
      setCount((c) => c + 1);
      setFlag((f) => !f);
    });
    // Next (promise)클릭 시 console에 의해 'render'가 두번 찍힘.
  }

  return (
    <div>
      <button onClick={handleSetTimeoutClick}>Next (setTimeout)</button>
      <button onClick={handlePromiseClick}>Next (promise)</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

그렇다면 async, await을 사용할 때에는 batching이 어떻게 일어나게 될까요?

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    console.log("render");
  });

  async function handleAsyncClick() {
    setCount((c) => c + 1); 
    setFlag((f) => !f);
    // await을 만나기 전까지는 batching이 동작함.
    await 1;
    // await 이후에는 batching이 동작하지 않음.
    setCount((c) => c + 1);
    setFlag((f) => !f);
  }
  
  return (
    <div>
      <button onClick={handleAsyncClick}>Next (async)</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

await 이전까지는 정상적으로 batching이 되지만 await 이후에는 batching이 되지 않는 것을 확인 할 수 있습니다. async 함수 내에 await부분을 주석처리 하고 버튼을 클릭하면 렌더가 한번만 되는 것을 확인 할 수 있습니다.

이렇게 동작하는 이유는 자바스크립트 내부적으로 아래와 같이 코드가 변경되서 실행되는 것으로 예상하고 있습니다. 틀린 부분이 있다면 답글 부탁드립니다.

function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    console.log("render");
  });

  async function handleAsyncClick() {
    setCount((c) => c + 1); 
    setFlag((f) => !f);
    
    Promise
      .resolve(1)
      .then(() => {
        setCount((c) => c + 1);
        setFlag((f) => !f);
      });
  }
  
  return (
    <div>
      <button onClick={handleAsyncClick}>Next (async)</button>
      <h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
    </div>
  );
}

이런 이유들로 17버전에서는 batching 프로세스가 동작하지 않는 경우가 있었습니다. 18버전에서는 이런 상황들에서도 batching 프로세스가 동작되도록 변경이 되었는데 ReactDOM.render 메소드의 사용 방법이 달라져서 18버전에 맞게 적용을 해주어야합니다. 만약 18버전의 render 방식으로 하지 않으면 17버전으로 동작하게 됩니다.

3. 18버전에서 batching 적용을 하고 싶지 않을 경우

개발을 하다보면 이전 상태에 의존성을 가진 상황을 구현해야 할 때가 있습니다. 이 경우에는 batching 프로세스가 동작하지 않도록 막아야 할 필요성이 있습니다. batching 프로세스를 막는 방법은 아래와 같이 flushSync 메소드를 사용해서 막을 수 있습니다.

// batching을 막고 싶을 때
import { flushSync } from 'react-dom'; // Note: react-dom, not react

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setFlag(f => !f);
  });
  // React has updated the DOM by now
}

4. 18버전 적용 방법

18 버전에 대한 적용 방법은 react 공식문서에 자세히 나와있어서 링크로 대체하겠습니다.

https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-client-rendering-apis

 

How to Upgrade to React 18 – React Blog

As we shared in the release post, React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. Please report an

reactjs.org

참고 자료

https://github.com/reactwg/react-18/discussions/21

https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#automatic-batching

https://github.com/reactwg/react-18/discussions/21

반응형
댓글