본문 바로가기
Frontend/React

[React] useState의 비동기 처리와 함수형 업데이트

by 오이가지아빠 2021. 7. 11.

#1. setState는 비동기로 동작한다

아래와 같이 동작하는 코드를 보자.

하나는 숫자를 1씩 증가시키는 버튼이고, 하나는 숫자를 1씩 감소시키는 버튼이다.

 

import React, { useState } from "react"

function App() {

  const [num, setNum] = useState(1)

  async function plus() {
    setNum(num + 1)
  }

  async function minus() {
    setNum(num - 1)
  }

  return (
    <div className="App">
      <h1>{num}</h1>
      <button onClick={plus}>PLUS</button>
      <button onClick={minus}>MINUS</button>
    </div>
  );
}

export default App;

아직까지는 잘 작동하고 있지만, 요건이 바뀌어서 1씩 증가하던것을 3씩 증가시키기 위해 아래처럼 변경했다고 치자.

import React, { useState } from "react"

function App() {

  const [num, setNum] = useState(1)

  async function plus() {
    setNum(num + 1)
    setNum(num + 1)
    setNum(num + 1)
  }

  async function minus() {
    setNum(num - 1)
  }

  return (
    <div className="App">
      <h1>{num}</h1>
      <button onClick={plus}>PLUS</button>
      <button onClick={minus}>MINUS</button>
    </div>
  );
}

export default App;

setNum(num + 1) 을 3번 호출했기 때문에 버튼 한번으로 3씩 증가할것으로 예상되지만, 여전히 1씩 증가하게 된다.

왜 그럴까?

 

동일한 state를 연속적으로 업데이트 하는 경우, 리액트는 setState를 각각 동기로 수행하지 않고, batch 처리한다.

전달된 setState를 하나로 병합한 후 최종적으로 한번의 setState를 하게 된다는 얘기다.

위의 결과처럼 병합을 수행할 때, 동일한 key에 대해 이전의 값을 계속 덮어 쓰기 때문에, 결국 마지막 명령어만 수행되는 셈이다.

 

#2. 함수형 업데이트

결국 문제의 해결은 setState를 비동기로 수행할 때, 값을 전달하지말고 업데이트된 최신의 state와 함께 함수를 전달하는 방법이다.

react의 setState코드를 보면 다음과 같이 값을 받거나, 이전 state와 함께 함수를 전달받을 수 있도록 되어있다.

    // React Hooks
    // ----------------------------------------------------------------------

    // based on the code in https://github.com/facebook/react/pull/13968

    // Unlike the class component setState, the updates are not allowed to be partial
    type SetStateAction<S> = S | ((prevState: S) => S);

이렇게 되면, 여러번 전달받는 함수들은 큐에 저장되어 순서대로 실행된다. 따라서 큐에서 먼저 수행된 함수의 결과로 반영된 state값이 다음 수행할 함수의 인자로 들어가게 되므로, 항상 최신의 state를 유지하게 된다.

import React, { useState } from "react"

function App() {

  const [num, setNum] = useState(1)

  async function plus() {
    setNum(num => num + 1)
    setNum(num => num + 1)
    setNum(num => num + 1)
  }

  async function minus() {
    setNum(num - 1)
  }

  return (
    <div className="App">
      <h1>{num}</h1>
      <button onClick={plus}>PLUS</button>
      <button onClick={minus}>MINUS</button>
    </div>
  );
}

export default App;

이렇게 하게되면 아래와 같이 정상적으로 동작하는 것을 확인할 수 있다.

 

반응형

댓글