React 16. Queueing a Series of State Updates

뚜루미·2024년 3월 21일

React

목록 보기
16/39
post-thumbnail

상태 변수를 설정하면 다른 렌더링이 대기열에 추가됩니다. 그러나 때로는 다음 렌더링을 대기열에 넣기 전에 값에 대해 여러 작업을 수행해야 할 수도 있습니다. 이렇게 하려면 React가 상태를 일괄 업데이트하는 방법을 이해하는 것이 도움이 됩니다.

React batches state updates

“+3” 버튼을 클릭하면 setNumber(number + 1) 카운터가 세 번 호출되므로 카운터가 세 번 증가할 것으로 예상할 수 있습니다.

import { useState } from 'react';

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

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

그러나 이전 섹션에서 기억할 수 있듯이 각 렌더링의 상태 값은 고정되어 있으므로 setNumber(1) 호출 횟수에 관계 없이 첫번째 렌더링의 이벤트 핸들러 내부 number 값은 항상 0 입니다.

그러나 여기에는 또 다른 요소가 하나 더 있습니다. React는 상태 업데이트를 처리하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다립니다. 이것이 바로 이러한 모든 setNumber() 호출 후에만 리렌더링이 발생하는 이유입니다

이는 레스토랑에서 주문을 받는 웨이터를 연상시킬 수 있습니다. 웨이터는 첫 번째 요리가 나오자 주방으로 달려가지 않습니다! 대신 주문을 완료하고, 내용을 변경할 수 있으며, 심지어 테이블에 있는 다른 사람의 주문을 받을 수도 있습니다.

이를 통해 너무 많은 리렌더링을 트리거하지 않고도 여러 컴포넌트에서도 여러 상태 변수를 업데이트할 수 있습니다. 하지만 이는 이벤트 핸들러와 그 안의 코드가 완료될 때까지 UI가 업데이트되지 않는다는 의미이기도 합니다. 일괄 처리 라고도 하는 이 동작을 통해 React 앱이 훨씬 빠르게 실행됩니다. 또한 변수 중 일부만 업데이트되어 혼란스러운 "반쯤 완성된" 렌더링을 처리하는 것을 방지합니다.

React는 클릭과 같은 여러 의도적인 이벤트를 일괄 처리하지 않습니다. 각 클릭은 별도로 처리되며 React는 일반적으로 안전한 경우에만 일괄 처리를 수행합니다. 예를 들어, 첫 번째 버튼 클릭으로 양식이 비활성화된 경우 두 번째 클릭 시 해당 양식이 다시 제출되지 않습니다.

Updating the same state multiple times before the next render

흔하지 않은 사용 사례이지만 다음 렌더링 전에 동일한 상태 변수를 여러 번 업데이트하려는 경우 setNumber(number + 1)와 같이 다음 상태 값을 전달하는 대신, setNumber(n => n + 1) 와 같이 이전 상태를 기반으로 다음 상태를 계산하는 함수를 대기열에 전달하면 됩니다. React를 단순히 대체하는 대신 “상태 값으로 무엇인가를 하라”고 전달하는 방법입니다.

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>
    </>
  )
}

n => n + 1 은 업데이트 함수입니다. 상태 설정자에 전달하는 경우 :

  1. 리엑트는 이벤트 핸들러의 다른 모든 코드를 모두 실행된 후 해당 함수가 처리되도록 대기열에 넣습니다.
  2. 다음 렌더링 동안 React는 대기열을 거쳐 최종 업데이트된 상태를 제공합니다.
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);

이벤트 핸들러를 실행하는 동안 React가 다음 코드 줄을 통해 작동하는 방법은 다음과 같습니다.

  1. setNumber(n => n + 1) : n => n + 1 는 함수입니다. React는 이를 대기열에 추가합니다.
  2. setNumber(n => n + 1) : n => n + 1 는 함수입니다. React는 이를 대기열에 추가합니다.
  3. setNumber(n => n + 1) : n => n + 1 는 함수입니다. React는 이를 대기열에 추가합니다.

다음 렌더링을 중 useState를 호출하면 React는 대기열을 통과합니다. 이전 number 상태는 0이었으므로, React는 n 인수로 첫 번째 업데이트 함수를 전달합니다. 그러면 React는 이전 업데이트 함수의 반환값을 n으로 받아 다음 업데이트 함수에 전달합니다.

queued updatenreturns
n => n + 100 + 1 = 1
n => n + 111 + 1 = 2
n => n + 122 + 1 = 3

React 는 최종적으로 3 을 최종 결과로 저장하고 useState에서 반환합니다.

이것이 “+3” 을 위의 예시에서 클릭했을 때 값이 3씩 증가하는 이유입니다.

What happens if you update state after replacing it

이러한 이벤트 핸들러에서는 number가 다음 렌더링에서 어떻게 렌더링될까요?

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>
    </>
  )
}

이 이벤트 핸들러가 React에게 수행할 작업은 다음과 같습니다.

  1. setNumber(number + 5) : number0이므로, setNumber(0 + 5)가 되고 React는 대기열에 “5로 대체”를 추가합니다.
  2. setNumber(n => n + 1) : n => n + 1 는 업데이트 함수입니다. React는 이 함수를 대기열에 추가합니다.

다음 렌더링 동안 React는 상태 큐를 아래와 같이 통과합니다.

queued updatenreturns
”replace with 5”0 (unused)5
n => n + 155 + 1 = 6

React는 최종 결과로 6을 저장하고 useState에서 반환합니다.

setState(5)는 실제로 setState(n => 5) 로 작동하며 n은 사용되지 않습니다.

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>
    </>
  )
}

이 이벤트 핸들러를 실행하는 동안 React가 작동하는 방법은 다음과 같습니다.

  1. setNumber(number + 5): number0이므로 setumber(0 + 5)가 되고React는 대기열에 "5로 대체"를 추가합니다.
  2. setNumber(n => n + 1): n => n + 1은 업데이터 함수이며, React는 해당 함수를 큐에 추가합니다.
  3. setNumber(42): React는 대기열에 "42로 대체"를 추가합니다.

다음 렌더링 동안 React는 상태 큐를 아래와 같이 통과합니다.

queued updatenreturns
”replace with 5”0 (unused)5
n => n + 155 + 1 = 6
”replace with 42”6 (unused)42

React는 최종 결과로 42을 저장하고 useState에서 반환합니다.

요약하자면, setNumber 상태 설정자에게 전달하는 내용을 생각하는 방법은 다음과 같습니다.

  • 업데이트 기능(예: n => n + 1)이 대기열에 추가됩니다.
  • 다른 값(예: number 5) 5는 이미 대기열에 있는 항목을 무시하고 “replace with”를 대기열에 추가합니다.

이벤트 핸들러가 완료된 후 React는 리렌더링을 트리거합니다. 리렌더링을 하는 동안 React는 대기열을 처리합니다. 업데이트 기능은 렌더링 중에 실행되므로 업데이트 기능은 순수해야 하며 결과만 반환해야 합니다. 내부에서 상태를 설정하거나 다른 sideeffect를 실행하려고 시도해선 안됩니다.

Naming conventions

해당 상태 변수의 첫 글자로 업데이트 함수 인수의 이름을 지정하는 것이 일반적입니다.

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

더 자세한 코드를 선호하는 경우 또 다른 일반적인 규칙은 setEnabled(enabled => !enabled)와 같은 전체 상태 변수 이름을 반복하거나 setEnabled(prevEnabled => !prevEnabled)와 같은 접두사를 사용하는 것입니다.

0개의 댓글