[리액트 공식문서 읽기] ADDING INTERACTIVITY - Queueing a Series of State Updates

JaeHong Jeong·2023년 9월 7일
post-thumbnail

Overview

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

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

하지만 이전 섹션에서 기억할 수 있듯이 각 렌더링의 상태 값은 고정되어 있으므로 첫번째 렌더의 이벤트 핸들러 내부의 number 값은 setNumber(1) 를 몇 번 호출하더라도 항상 0 이다.

setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);

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

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

This lets you update multiple state variables—even from multiple components—without triggering too many re-renders. But this also means that the UI won’t be updated until after your event handler, and any code in it, completes. This behavior, also known as batching, makes your React app run much faster. It also avoids dealing with confusing “half-finished” renders where only some of the variables have been updated.

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

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

Updating the same state multiple times before the next render

흔하지 않은 사용 사례이지만 다음 렌더링 전에 동일한 상태 변수를 여러번 업데이트하려는 경우 setNumber(number + 1) 와 같은 다음 상태 값을 전달하는 대신 다음 상태를 계산하는 함수를 전달할 수 있다. setNumber(n => n + 1) 와 같이 대기열의 이전 항목을 기반으로 한다. 이는 단순히 상태 값을 바꾸는 대신 리액트에게 “상태 값으로 뭔가를 하라”고 지시하는 방법이다.

카운터를 늘려보아라.

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. 다음 렌더링 동안 리액ㅌ는 대기열을 통과하여 최종 업데이트된 상태를 제공한다.
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);

이번트 핸들러를 실행하는 동안 리액트가 이러한 코드 줄을 통해 작동하는 방법은 다음과 같다.

  1. setNumber(n => n + 1)n => n + 1 은 함수이다. 리액트는 이를 큐에 추가한다.
  2. setNumber(n => n + 1)n => n + 1 은 함수이다. 리액트는 이를 큐에 추가한다.
  3. setNumber(n => n + 1)n => n + 1 은 함수이다. 리액트는 이를 큐에 추가한다.

다음 렌더링 중에 useState 를 호출하면 리액트는 대기열을 통과한다. 이전 number0 이었으므로 리액트는 첫 번째 업데이트 함수에 n 을 인수로 전달한다. 그런 다음 리액트는 이전 업데이트 함수의 반환 값을 가져와 다음 업데이트에 n 으로 전달한다.

리액트는 3 을 최종 결과로 저장하고 useState 에서 반환한다.

위 예에서 “+3”을 클릭하면 값이 3만큼 올바르게 증가하는 이유이다.

What happens if you update state after replacing it

이 이벤트 핸들러는 어떨까? 다음 렌더링에서는 number 가 어떻게 될 것이라고 생각하나

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>
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>
    </>
  )
}

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

  1. setNumber(number + 5)number0 이므로 setNumber(0 + 5) 이다. 리액트 대기열에 ‘5로 바꾸기’를 추가한다.
  2. setNumber(n => n + 1)n => n + 1 은 업데이트 함수다. 리액트는 해당 함수를 대기열에 추가한다.

다음 렌더링 동안 리액트는 상태 큐를 통과한다.

리액트는 6을 최종 결과로 저장하고 useState 에서 반환한다.

💡 Note

setState(5) 는 실제로 setState(n => 5) 처럼 작동하지만 n은 사용되지 않는다는 것을 눈치챘을 것이다.

What happens if you replace state after updating it

한 가지 예를 더 들어보면, 다음 렌더링에서는 number 가 어떻게 될 것이라고 생각하나

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);
}}>
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>
    </>
  )
}

Here’s how React works through these lines of code while executing this event handler:

  1. setNumber(number + 5)number is 0, so setNumber(0 + 5). React adds “replace with 5 to its queue.
  2. setNumber(n => n + 1)n => n + 1 is an updater function. React adds that function to its queue.
  3. setNumber(42): React adds “replace with 42 to its queue.

During the next render, React goes through the state queue:

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

  1. setNumber(number + 5)number0 이므로 setNumber(0 + 5) 이다. 리액트는 대기열에 “5 로 바꾸기”를 추가한다.
  2. setNumber(n => n + 1)n => n + 1 은 업데이트 함수이다. 리액트는 해당 함수를 대기열에 추가한다.
  3. setNumber : 리액트는 대기열에 “42로 대체”를 추가한다.

다음 렌더링 동안 리액트는 상태 큐를 통과한다.

그런 다음 리액트는 42 를 최종 결과로 저장하고 useState 를 반환한다.

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

  • 업데이터 함수 (예 : n => n + 1 )이 대기열에 추가된다.
  • 다른 값 (예 : 숫자 5 )은 이미 대기열에 있는 항목을 무시하고 대기열에 ‘5 로 바꾸기’를 추가한다.

이벤트 핸들러가 완료된 뒤 리액트는 다시 렌더링을 트리거한다. 다시 렌더링하는 동안 리액트는 대기열을 처리한다. 업데이트 함수는 렌더링 중에 실행되므로 업데이트 함수는 순수해야 하며 결과만 반환해야 한다. 내부에서 상태를 설정하거나 다른 부작용을 실행하려 하지 마라. Strict Mode에서 리액트는 실수를 찾는 데 도움이 되도록 각 업데이트 기능을 두 번 실행한다.

Naming conventions

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

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

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

Recap

  • 상태 설정은 기존 렌더링의 변수를 변경하지 않지만 새 렌더링을 요청한다.
  • 리액트는 이벤트 핸들러 실행이 완료된 후 상태 업데이트를 처리한다. 이를 batching(일괄 처리)라고 한다.
  • 하나의 이벤트에서 일부 상태를 여러 번 업데이트 하려면 setNumber(n => n + 1) 업데이트 함수를 사용하면 된다.
profile
반갑습니다.

0개의 댓글