[React] state batch

Suh, Hyunwook·2022년 2월 2일
1

리액트의 useState를 통한 상태변경은 다음과 같은 특징이 있다.

  1. setState를 통해 상태를 변경하여도, 각 state들은 변경 이전의 값을 참조하고 있다. 즉, 변경된 상태는 렌더링 이후, 반영이 된다는 것이다. (state 변경 -> re-rendering trigger -> 변경된 state 반영)
  2. setState를 해도, 바로 re-rendering하지 않고, 여러 setState들을 묶어서 한번에 처리한다. 즉, 이를 batching이라고 한다.
  3. 단, promise, fetch, setTimeout 등이 중첩되는 경우 batching이 되지 않는다.

이에 따라, 여러가지 예제를 보려고 한다.

예제(1)

import React, { useState, Fragment } from 'react';
import ReactDOM from 'react-dom';

function Component () {
  const [item1, setItem1] = useState("initial item 1");
  const [item2, setItem2] = useState("initial item 2");

  console.log("상태변경된 결과:", item1, item2);
  
  function handleClickWithPromise() {
    Promise.resolve().then(() => {
      setItem1("updated item 1");
      setItem2("updated item 2");
    })
  }

  function handleClickWithoutPromise () {
    setItem1("updated item 1");
    setItem2("updated item 2");
    console.log("상태변경되었을까요?",item1, item2);
  }
  
  return (
    <Fragment>
      <button onClick={handleClickWithPromise}>
        {item1} - {item2}
      </button>
      <button onClick={handleClickWithoutPromise}>
        {item1} - {item2}
      </button>
    </Fragment>
  )
}

function App () {
  return <Component />;
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App/>, rootElement);

handleClickWithPromise를 누를 경우 상세 flow는 다음과 같다.

  • setItem1이 실행 -> re-render -> console: "상태변경: updated item 1 initial item 2" -> setItem2이 실행 -> re-render -> console: "상태변경: updated item 1 updated item 2"

handleClickWithoutPromise를 누를 경우 상세 flow는 다음과 같다.

  • setItem1 인식 -> setItem2 인식 -> console: "상태변경되었을까요?" initial 1 initial item 2 -> console: "상태변경: updated item 1 updated item 2"

첫번째 버튼의 경우, promise 안에 있기 때문에 batching이 작동하지 않은 반면, 두번째의 경우, state batching이 이뤄지게 되어, re-rendering이 한번만 일어났으며, 동시에 상태가 변경됨을 확인할 수 있었다. 이는 setState가 event queue에 저장되고, 16ms마다 실행하게 되는, 비동기적 처리가 되고 있기 때문이다.

더불어, handleClickWithoutPromise 함수 내부에 있던 console.log의 경우, setItem을 실행한다 하더라도, 여전히 첫 렌더링 시 상태를 참조하고 있기 때문에(즉, useState 초기값), 위치상 setItem 뒤에 있지만, 변화된 state를 log하지 못한다.

예제(2)

import { useState } from 'react';

function DoubleIncreaser() {
  const [count, setCount] = useState(0);
  const doubleIncreaseHandler = () => {
    setCount(actualCount => actualCount + 1);
    setCount(actualCount => actualCount + 1);
    //setCount(count+1);
    //setCount(count+1);
  };
  
  return (
    <>
      <button onClick={doubleIncreaseHandler}>
        Double Increase
      </button>
      <div>Count: {count}</div>
    </>
  );
}

더불어, doubleIncreaser에는 setCount(actualCount => actualCount + 1);에 콜백함수를 대신 넣는데, setCount(count+1); 와 같이 실행을 할 경우, 두 번째 setCount에서 여전히 초기값인 0을 참조하기 때문이다. 따라서 답이 1이 되는 반면, actualCount의 경우, count 값을 할당받게 되는데, 이 경우, doubleIncreaser context 내에서 증가하는 값을 바로 반영할 수 있고, 이에 답은 2를 출력하게 된다.

0개의 댓글