[ React ] useState 의 렌더링과 함수형 업데이트

Maria Kim·2022년 1월 7일
post-thumbnail

useState의 함수형 업데이트와의 렌더링 시점을 알기 위해 여러 코드를 작성해 봤고 이를 통해 알아낸 부분들을 정리한 글이다.

setState의 비동기적 특성 vs 함수형 업데이트

setState는 비동기로 동작한다

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

  const onClick = () => {
    setValue(value+1)
    setValue(value+1)
    setValue(value+1)
  }
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <h1>{value}</h1>
    </div>
  );
}

문제 : 위에서 버튼을 클릭하면 어떤 값이 나올까? 3 일까?

답 은 1 이다.

이유

  • 이는 seState의 비동기적 특성 때문이다.
  • 리액트가 효율적으로 렌더링하기 위해 여러 개의 상태 값 변경 요청을 batch(일괄 처리) 처리하기 때문이다.
  • batch 처리의 이유는 당연히 성능 이슈 때문이다. -> 리렌더링을 발생하는 setState가 일어날 때마다 리렌더링이 일어난다면 얼마나 많은 렌더링이 일어나겠는가? 그렇다면 setState를 사용하기가 두려워질 수도 있을 듯하다.

설명

  • 용어 정의
    A : 이번 렌더링의 초기(시작) state 값
    B : 저장하여 다음 렌더링에 사용될 state 값
// A = 0

setValue(value+1)
// ① B = A + 1 => 2

setValue(value+1)
// ② B = A + 1 => 2

setValue(value+1)
// ③ B = A + 1 => 2
  • 이렇게 setState는 변경된 사항을 기억하지 않기 때문에 마지막 업데이트만 적용되어 다름 렌더링에 쓰이게 된다.

setState 함수형 업데이트

문제 : 하지만 setState 를 동기적으로 사용하는 방법은 없을까

답 함수형 업데이트를 사용하자!

방법

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

  const onClick = () => {
    setValue(prev => prev+1)
    setValue(prev => prev+1)
    setValue(prev => prev+1)
  }
  
  return (
    <div className="App">
      <button onClick={onClick}>+</button>
      <h1>{value}</h1>
    </div>
  );
}

설명

  • 용어 정의
    A : 이번 렌더링 초기(시작) state 값
    B : A 또는 이번 렌더링에서 업데이트된 값 / 다음 렌더링에 사용될 값
    C : 함수형 업데이트를 통해 생겨난 값
// A = 0
// B = A = 0

setValue(prev => prev+1)
// ①-1 : C = B + 1 => 1 
// ①-2 : C 를 B 에 저장

setValue(prev => prev+1)
// ②-1 : C = B + 1 => 2
// ②-2 : C 를 B 에 저장

setValue(prev => prev+1)
// ③-1 : C = B + 1 => 3
// ③-2 : C 를 B 에 저장
  • 함수현 업데이트는 이렇게 업데이트된 값을 저장하기 때문에 여러번의 업데이트가 적용되어 다음 렌더링에 사용된다.

함수형 업데이트를 한 상태를 다른 useState 에서 어떤 값이 사용될까?

문제 : X useState 을 함수형 업데이트하고 Y useState의 상태를 업데이트데 X state 를 사용한다면 업데이트된 X state 를 받을 수 있을까?

고민 코드

export default function App() {
  const [valueX, setValueX] =  useState(0)
  const [valueY, setValueY] =  useState(0)

  const onClick = (e) => {
    setValueX(prev => prev + 1);
    setValueX(prev => prev + 1);
    setValueY(valueX + 1)
  }

  return (
    <div className="App"  >
      <button onClick={onClick}>+</button>
      <h1>valueX : {valueX}</h1>
      <h1>valueY : {valueY}</h1>
    </div>
  );
}
  • 이 코드에서 '+'버튼을 클릭하면 valueY는 어떤 값을 보여줄까?
  • valueX 의 2가지 상태
    A : 이번 렌더링의 초기(시작) state 값
    B : 저장하여 다음 렌더링에 사용될 state 값

따라서 2가지의 경우가 나온다
case 1. A(0) + 1 = 1
case 2. B(2) + 1 = 3

답

  • case 1이 정답이다. valueY 는 1으로 나온다
  • Y 는 업데이트된 X의 값이 아닌 X의 이번 렌더링 초깃값을 사용하게 사용함을 의미한다.

설명

용어 정리

  • X useState -
    A : 이번 렌더링 초기(시작) state 값 = 0
    B : A 또는 이번 렌더링에서 업데이트된 값 / 다음 렌더링에 사용될 값
    C : 함수형 업데이트를 통해 생겨난 값

  • Y useState -
    D : 이번 렌더링의 초기(시작) state 값
    E : 저장하여 다음 렌더링에 사용될 state 값

setValueX(prev => prev + 1);
// ①-1 처음 setState 시 B는 A => B = 0 
// ①-2 C = B(0) + 1 = 1 
// ①-3 C 를 B에 저장
// ①-4 B = 1

setValueX(prev => prev + 1);
// ②-1 C = B(1) + 1 = 2
// ②-2 C 를 B에 저장 
// ②-3 B = 2  

setValueY(valueX + 1)
// ③-1 E = A + 1 = 1

여러 개의 useState를 업데이트했다면 과연 렌더링은 몇번 일어날까?

문제 : input에 a 를 input 에서 작성하여 1번의 onChange가 호출되었을 때 몇 번의 render 가 일어날까?

코드

export default function App() {
  const [valueX, setValueX] =  useState(0)
  const [valueY, setValueY] =  useState(0)
  const [valueZ, setValueZ] =  useState(0)

  const onChange = (e) => {
    setValueX(prev => prev + 1);
    setValueY('')
    setValueZ(e.target.value);
  }

  return (
    <div className="App"  >
     <input onChange={onChange}/>
      <h1>valueX : {valueX}</h1>
      <h1>valueY : {valueY}</h1>
      <h1>valueZ : {valueZ}</h1>
    </div>
  );
}

답

  • 1번의 렌더링만 일어난다.
  • setState가 호출되면 리렌더링이 되긴 하지만 그 즉시 리렌더링이 일어난 것이 아닌 JS Call Stack 이 모두 실행되고 리렌더링이 일어난다. 한꺼번에 setState들의 업데이트가 이루어지며 리렌더링 되는 것이다.
profile
Developer, who has business in mind.

0개의 댓글