setState가 비동기 처리되는 이유

Hyun·2021년 9월 21일
0

리액트 기초

목록 보기
2/18

상태 관리의 비동기성

React는 상태를 바탕으로 view를 그리기 때문에 일반 변수로 상태를 할당하지 않고 setState로 상태를 할당한다. 하지만 javascript처럼 console.log로 상태 변화를 확인하려 하면 이상한 부분이 있다는 걸 발견할 수 있다. 그것은 setState가 비동기적으로 처리되기 때문인데, 왜 React가 상태 변화를 비동기적으로 관리하는지 알아보겠다.

흔히 겪는 실수

상태를 처음 접하면 많이들 아래와 같은 코드로 변경된 상태를 확인하려한다.

import React, { useState } from "react";

function App() {
  const [state, setState] = useState(1);

  const upState = () => {
    setState(state + 1);
    console.log(state);//<-이 부분
    //upState안에서 setState가 더 호출될 여지가 있기때문에
    //setState함수가 호출된다고 해서 state가 즉시 update가 되지는 않는다. 
    //upState함수 호출이 종료되고 난 후 변경된 상태 값들이 한꺼번에 batch update(일괄 업데이트)가 이루어진다.  
    //따라서 아직 state가 update되지 않았기 때문에 console.log(state)를 하면 이전 값이 출력되는 것!!!
  };

  return (
    <div className="App">
      <button onClick={upState}>상태값 UP</button>
      <p>상태값은 {state}</p>
    </div>
  );
}

export default App;

상태를 잘 모르는 React 유저들은 버튼을 누르면 변경한 상태값이 정확히 랜더링 되기를 기대했을 것이다. 하지만 이상하게도 console.log(state)값은 이전 상태값을 출력한다. 이러한 문제 때문에 setState 이후 바로 상태값을 참조하여 다른 작업을 할 때 문제가 생기게 된다. 그리고 이러한 문제를 해결하기 위해서 react-thunk, react-saga등을 사용해 동기화 처리를 해준다. 그렇다면 왜 React는 상태값을 비동기적으로 처리하게 만들었을까?

React batch update(일괄 업데이트)

React는 batch update를 16ms단위로 진행한다. 이말은 즉슨 16ms동안 변경된 상태 값들을 모아서 단 한번 리랜더링을 진행한다는 것을 의미한다. 이러한 행동은 웹페이지 렌더링 횟수를 줄여 좀 더 빠른 속도로 동작하게끔 만든다. Redux의 창시자인 Dan Abramov의 말에 따르면, 현재 batch update의 적용은 setState reaction 이벤트 핸들러 내부의 업데이트만 기본적으로 해당된다고 한다..

event hanlder에서 batch update를 사용한 예제를 살펴보자

import React, { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const [counter1, setCounter1] = useState(0);
  const [counter2, setCounter2] = useState(0);
  const [counter3, setCounter3] = useState(0);
  const [renderCount, setRenderCount] = useState(0);

  useEffect(() => {
    setRenderCount(renderCount + 1);
  }, [counter1, counter2, counter3]);

  const handleClick = () => {
    setCounter1(counter1 + 1);
    setCounter2(counter2 + 1);
    setCounter3(counter3 + 1);
  }

  return (
    <div className='App'>
      <h1>Function Component</h1>
      <div>
        Counter1: {counter1}
      </div>
      <div>
        Counter2: {counter2}
      </div>
      <div>
        Counter3: {counter3}
      </div>
      <br/>
      <div>Component was rendered {renderCount} times</div>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}


위와 같이 setCounter1, 2, 3 총 3번의 setState호출을 한번의 이벤트 핸들러 함수 호출을 통해 구현하였다. 이때 setCounter1, 2, 3는 일괄 업데이트 됨으로써 버튼을 3번 클릭했을 때 총 컴포넌트 렌더링이 4번밖에 일어나지 않은 것을 볼 수 있다. 이는 클래스형 컴포넌트에게도 동일하게 적용된다.

앞서 언급했듯이 일괄 업데이트는 setState reaction 이벤트 핸들러 내부에서 작동해야 한다. 하지만 실제로 다른 방법에서도 효과가 있다. 아래 예제에서 useEffect내에서 batch update가 일어나는 모습을 살펴볼 수 있다.

import React, { useState, useEffect } from "react";
import "./styles.css";

export default function App() {
  const [toggle, setToggle] = useState(false);
  const [counter1, setCounter1] = useState(-1);
  const [counter2, setCounter2] = useState(-1);
  const [counter3, setCounter3] = useState(-1);
  const [renderCount, setRenderCount] = useState(0);

  useEffect(() => {
    setCounter1(counter1 + 1);
    setCounter2(counter2 + 1);
    setCounter3(counter3 + 1);
  }, [toggle]);

  useEffect(() => {
    setRenderCount(renderCount + 1);
  }, [counter1, counter2, counter3]);

  const handleClick = () => {
    setToggle(!toggle);
  }

  return (
    <div className='App'>
      <h1>Function Component</h1>
      <div>
        Counter1: {counter1}
      </div>
      <div>
        Counter2: {counter2}
      </div>
      <div>
        Counter3: {counter3}
      </div>
      <br/>
      <div>Component was rendered {renderCount} times</div>
      <button onClick={handleClick}>Click me</button>
    </div>
  );
}


위와 같이 버튼을 누르면 handleClick함수가 호출되고 toggle이 !toggle로 바뀐다. 이에 따라
useEffect의 setCounter1, 2, 3이 호출되고, 이어서 또다른 useEffect의 setRenderCount함수가 호출된다. 이를 통해 batch update가 일어나는 16ms안에 useEffect들의 상태 값들이 변경된다면 이것을 모아 한꺼번에 batch update(일괄 처리)한 것을 알 수 있다.

하지만 이 기능이 모든 상황에서 작동하는 것은 아니다. 다음과 같은 종류 async/await, then/catch, setTimeout, fetch의 비동기 작업을 사용하는 이벤트 핸들러에서는 별도의 상태 업데이트가 수행되지 않는다.

참고
seongkyun/velog
Nitai Aharoni/medium

profile
better than yesterday

0개의 댓글