Queueing a Series of State Updates

박재현·2024년 2월 21일
0
post-thumbnail

state변수를 설정하면 다음 렌더링이 큐에 들어가는데, 경우에 따라서 다음 렌더링을 큐에 넣기 전에 값에 대해 여러 작업을 수행하고 싶을때가 있다. (렌더링 기다리지 않고 바로 업데이트된 값을 사용하고 싶을때!)

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 5);
        setNumber(number + 10);
      }}>+3</button>
    </>
  )
}

위와같은 코드가 있을때, button을 클릭할때마다 16씩 number가 증가하기를 기대하지만, 사실상 버튼을 누를때마다 10씩 증가하게 된다.

왜냐하면 setNumber(number + 1), setNumber(number + 5), setNumber(number + 10) 이렇게 3번 호출이 되지만 실제로는 아직 re-rendering이 이루어 지지 않아 각각의 setNumber에 들어가는 number의 값은 initial value인 0이 들어가기 때문이다.

아니근데, 여기서 의문이 생기는데 아직 re-rendering이 발생하지 않아서 number에 0이 들어가는건 알겠어, 근데 왜 16이 아니라 10씩 증가하는거임??

왜냐하면 React는 state 업데이트를 하기전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다리기 때문임!
그래서 re-rendering은 모든 setNumber()가 호출된 완료에 이후에 일어나므로, 마지막의 setNumber(number + 10)이 수행되게 된다.

리액트는 손님에게 주문을 받는 웨이터라고 했는데 손님이 만약
콜라주세요아 아니다 사이다 주세요아니다 그냥 마운틴듀 주세요 라고 연달아서 웨이터에게 주문하면, 콜라+사이다+마운틴듀를 주는게 아니라 마지막에 주문한 마운틴듀를 갖다주지 않나?!

사실 성능측면에서 생각을 해보면 setNumber(number + 1), setNumber(number + 5), setNumber(number + 10) 이렇게 연달아 3번 수행하게 되면 re-rendering을 3번해야한다.

그럼 어제 정리했던 react life cycle을 다시한번 상기시켜보면 re-rendering을 위해서 getDerivedStateFromProps가 일어나고 shouldUpdate를 확인하고 render -> DOM update 와 같은 과정이 불필요하게 일어날거고 이는 performance 측면에서 lose라고 생각한다.

또 단순히 여기서는 state가 number 하나뿐이지만, 다수의 state가 존재할때는 더욱 lose가 되겠지?

따라서 이벤트 핸들러의 모든 코드가 실행될때까지 기다리면, 너무 많은 re-rendering을 trigger 하지 않고 여러 component에서 다수의 state를 update할 수 있다.

Updating the same state multiple times before the next render

다음 렌더링이 되기전에 동일한 state를 여러번 업데이트 하고싶다면 setNumber(number + 1) 대신 setNumber(n => n + 1)처럼 큐의 이전 상태(previous state)를 기반으로 다음 상태(next state)를 계산하는 함수를 전달할 수 있다.
이는 단순히 state값을 대체하는게 아니라 React에게 state값으로 무언가를 해! 라고 지시하는 방법이다.

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}

여기서 setNumber(n => n + 1);는 다른글에서 정리한것 처럼 updater function이라고 한다.

그리고 이를 state setter(여기선 setNumber) 함수 인자로 전달할때,
1. React는 이벤트 핸들러의 다른 코드가 모두 실행된 후에 이 함수가 처리되도록 큐에 푸시한다
2. 다음 렌더링 중에 React는 큐를 순회해 최종 업데이트된 state를 제공한다

다음 렌더링 중에 useState를 호출하면 React는 큐를 순회하는데,
이전 number의 state는 0이므로, React는 첫번째 setNumber 함수인자 n에 0을 전달한다.
그 다음 React는 이전의 setNumber의 반환값을 가지고와서 다음 setNumber의 함수인자 n에 넣어준다.

What happens if you update state after replacing it

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
      }}>Increase the number</button>
    </>
  )
}

위 코드를 실행시켜보면 버튼울 누르면 number는 6씩 증가하게 된는데, 이벤트 핸들러가 React에게 지시하는 작업은 아래처럼 된다.
1. setNumber(number + 5) 초기 number값은 0이니 0 + 5값인 5를 큐에 푸시
2. 큐에서 이전 값인 5에 1을 더해서 6

What happens if you replace state after updating it

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
        setNumber(42);
      }}>Increase the number</button>
    </>
  )
}

위 코드가 실행되면 리액트는 아래와 같은 순서로 작동하게 될건데
1. setNumber(number + 5): number는 0이고 0+5인 5를 queue에 푸시
2. setNumber(n => n + 1): 업데이터 펑션이고, 큐에있는 5에 1을 더해서 6을 큐에 푸시
3. setNumber(42): 리액트는 number를 42로 바꾸라는 액션을 큐에 추가

결과적으로 number는 42가 된다.

Naming conventions

업데이터 함수 인수의 이름은 해당 state 변수의 첫글자로 지정하는것이 일반적이다.

setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);

좀 더 자세한 코드를 사용하고 싶을경우 setEnabled(enabled => !enabled)와 같이 전체 state의 이름을 반복하거나, setEnabled(prevEnabled => !prevEnabled)와 같이 접두사 previous를 사용하는게 일반적이다.


참고

profile
기술만 좋은 S급이 아니라, 태도가 좋은 A급이 되자

0개의 댓글

관련 채용 정보