useState 비동기 업데이트

조민호·2023년 1월 22일
0
post-custom-banner

setState함수는 이벤트 핸들러 내에서 비동기적으로 동작합니다


setState함수는 단순히 상태를 바꾸는 함수가 아니라, 바꿔달라고 요청해주는 함수입니다.

그러므로 하나의 이벤트 핸들러 내에서 setState가 여러 번 호출된다면, 이벤트 핸들러 함수가 끝난 시점에 state를 일괄적으로 업데이트하고 리렌더링합니다

“setStae가 여러개 있으면 마지막에 다 끝나고 일괄처리”
이렇게 이해하지 말고, “비동기로 처리”가 된다고 이해해야 하는 것이 더 정확합니다


여러개의 setState함수가 있을 때

  • setState함수 하나가 실행되고 리렌더링이 될 때까지 기다리고, 이어서 다음 업데이트 함수를 실행
    >> 동기
  • setState함수 하나가 실행되되면 바로 다음 setState함수를 실행, 최종적으로 이벤트핸들러가 종료되면 업데이트 요청된 상태들로 일괄적으로 한번에 리렌더링
    >> 비동기

리액트가 setState를 동기로 처리하면
하나의 상태값 변경 함수가 호출될 때마다 화면을 다시 그리기 때문에 성능 이슈가 생길 수 있으며,
만약 동기로 처리하지만 매번 화면을 다시 그리지 않는다면 UI 데이터와 화면 간의 불일치가 발생해서 혼란스러울 수 있습니다

import React, { useState} from 'react'

const App = () => {
   const [list, setList] = useState([])
   const [number, setNumber] = useState('')
   
    ...

   const onInsert=e=>{
      const nextList=list.concat(parseInt(number))
      setList(nextList)
      console.log('리스트 업데이트')
      setNumber('')*
   }

   return (
      <div>
        ...
      </div>
   )
}

export default App

위의 코드를 실제로 실행시켜 보면

  1. setList가 진행이 되고 나서 바로 리렌더링이 발생하고,
  2. 콘솔창에 ‘리스트 업데이트’ 가 찍히고
  3. setNumber가 진행이 되고 나서 다시 리렌더링이 발생해서

총 2번의 리렌더링이 발생할 것 같지만,

  1. 실제로는 콘솔창에 ‘리스트 업데이트’가 먼저 찍히고
  2. onInsert함수가 끝나고나서 setList 와 setNumber가 요청했던 상태 업데이트가 일괄적으로 진행이 돼서 한번에 리렌더링이 진행이 됩니다




또한 이벤트 핸들러 함수 안에서 또다른 이벤트 핸들러가 존재한다면

최종적으로 한 싸이클에서 호출된 모든 이벤트 핸들러가 종료되고 나서 배치로 처리가 됩니다

import React, { useState } from 'react'

const App = () => {
  const [input, setInput] = useState('')
  const [flag, setFlag] = useState(false)

  const onChange = (e) => {
    setInput(e.target.value)
    check()
  }

  const check = () => {
    setFlag((prev) => !prev)
    console.log(flag)
  }

  console.log('render')

  return (
    <div>
      <input value={input} onChange={onChange}></input>
    </div>
  )
}

export default App

위의 코드는

  • onChange()에서 setState함수를 사용하며 check()를 호출하고
  • check()에서도 setState함수를 사용합니다

여기서 onChange() 와 check()까지 전부 진행하고 난 다음에 상태 업데이트가 배치로 처리되는 것입니다




useState의 비동기적 속성과 batching


  • 리액트에서 setState를 사용하여 상태를 업데이트할 경우,
  • setState는 비동기적으로 작동하기 때문에 업데이트 된 상태가 setState호출 즉시 반영되는 것은 아니라고 했습니다
  • 업데이트 함수를 사용하고 해당 이벤트 핸들러 함수가 종료가 되면, 업데이트된 state로 리렌더링이 된 후에야 비로소 업데이트된 state가 반영되는 것이었습니다

이처럼 리액트의 state를 업데이트하는 데있어서, 비동기적으로 작동하는 속성은 여러개의 state를 다룰 때 퍼포먼스측면에서 유리합니다

반대로 만약 동기적으로 작동했다면 리렌더링 과정이 지나치게 많아집니다

state1 업데이트,리렌더링 - state2 업데이트,리렌더링 - state3 업데이트,리렌더링

그래서 비동기 방식을 이용해서 여러 state를 동시에 업데이트하는 경우,

리액트는 state를 batching하여 업데이트를 진행합니다

💡 Batching은 `전달된 오브젝트들을 하나로 합치는 작업` 이며 object composition 이라고도 불립니다

쉽게 말하지면, 데이터를 실시간으로 처리하는게 아니라, 일괄적으로 모아서 처리하는 작업을 의미합니다

가령, 하루동안 쌓인 데이터를 배치작업을 통해 특정 시간에 한꺼번에 처리하는 경우가 이에 해당합니다
은행의 정산작업과 같은 업무에서 이런 일괄처리를 수행하게 되며 사용자에게 빠른 응답이 필요하지 않은 서비스에 적용할 수 있으며, 특정 시간이후에는 자원을 거의 소비하지 않는 것이 특징입니다

  1. 대량의 데이터를 처리한다.
  2. 특정 시간에 프로그램을 실행한다.
  3. 일괄적으로 처리한다

즉, 여러 상태들을 한번에 합친 다음에 리렌더링-업데이트를 한다는 것입니다


이러한 특성 때문에 만약 같은 state를 여러번 업데이트하게 되면 우리가 예상하는 것과 다른 결과를 내놓게 되는 치명적인 경우가 있습니다

연속해서 동일한 state에 대한 setState 함수를 실행시킬 경우 Batching으로 인해 가장 마지막에 호출된 setState함수만 overwrite되어 호출 되는 것입니다

위의 setList , setNumber의 비동기 처리 예시는 , 같은 상태가 아니라 서로 다른 상태를 업데이트 하는 것이었지만 , 아래의 예시 같은 경우는 value라는 하나의 상태를 여러번 업데이트하고 있습니다

function App() {
  const [value, setValue] = useState(0)

  const onClick = () => {
    setValue(value+1)
    setValue(value+1)
    setValue(value+1)
  }

  return (
    <>
      <button onClick={onClick}>+</button>
      {value}
    </>
  )
}

export default App

여기서 한번의 클릭으로 value의 값이 3씩 늘어날 것을 예상했지만

실제로는 1씩 늘어나게 됩니다.

post-custom-banner

0개의 댓글