SPACE RUMI

Hi, I am rumi. Let's Splattack!

[STUDY] 스터디/React Docs

React docs beta 한글 번역 : Queueing a Series of State Updates - 일련의 상태 업데이트 대기

백루미 2023. 3. 1. 22:26
반응형

일련의 상태 업데이트 대기

state 변수를 setting하면 다른 렌더링이 대기열에 추가됩니다. 하지만 때로는 다음 렌더링을 대기열에 넣기 전에 값에 대해 여러 연산을 수행하고 싶을 수 있습니다. 이를 위해서는 React가 상태 업데이트를 일괄 처리하는 방법을 이해하는 것이 도움이 됩니다.

 

학습 내용

  • "일괄 처리"란 무엇이며 React가 이를 사용해 여러 상태 업데이트를 처리하는 방법
  • 동일한 상태 변수에 여러 업데이트를 연속으로 적용하는 방법

 

React 배치 상태 업데이트 

"+3" 버튼을 클릭하면 setNumber(숫자 + 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)을 몇 번 호출하든 항상 0입니다:

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

하지만 여기서 논의해야 할 다른 요소가 하나 더 있습니다. React는 상태 업데이트를 처리하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다립니다. 이것이 바로 모든 setNumber() 호출이 끝난 후에만 재렌더링이 일어나는 이유입니다.

이것은 식당에서 주문을 받는 웨이터를 떠올리게 할 수 있습니다. 웨이터는 첫 번째 요리가 나오자마자 주방으로 달려가지 않습니다! 대신, 주문을 완료하고, 주문을 변경하고, 테이블에 있는 다른 사람의 주문을 받기도 합니다.

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

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

 

다음 렌더링 전에 동일한 상태 변수를 여러 번 업데이트하기

흔한 사용 사례는 아니지만, 다음 렌더링 전에 동일한 상태 변수를 여러 번 업데이트하고 싶다면 setNumber(number + 1)처럼 다음 상태 값을 전달하는 대신, setNumber(n => n + 1)처럼 대기열의 이전 상태를 기반으로 다음 상태를 계산하는 함수를 전달할 수 있습니다. 이는 단순히 값을 대체하는 것이 아니라 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을 업데이터 함수라고 합니다. state setter에게 전달할 때:

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

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

setNumber(n => n + 1): n => n + 1은 함수입니다. React는 이를 큐에 추가합니다.
setNumber(n => n + 1): n => n + 1은 함수입니다. React는 이를 큐에 추가합니다.
setNumber(n => n + 1): n => n + 1은 함수입니다. React는 이를 큐에 추가합니다.


다음 렌더링 중에 useState를 호출하면 React는 큐를 통과합니다. 이전 숫자 상태는 0이었으므로 React는 이를 첫 번째 업데이터 함수에 n 인수로 전달합니다. 그런 다음 React는 이전 업데이터 함수의 반환값을 가져와서 다음 업데이터에 n으로 전달하는 식으로 반복합니다:

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

이것이 위 예제에서 "+3"을 클릭하면 값이 3씩 올바르게 증가하는 이유입니다.

상태를 교체한 후 업데이트하면 어떻게 되나요? 
이 이벤트 핸들러는 어떨까요? 다음 렌더링에서 숫자가 어떻게 될까요?

<button onClick={() => {
  setNumber(number + 5); // 0 + 5 = 5
  setNumber(n => n + 1); // 5 + 1 = 6
}}>
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에게 지시하는 내용은 다음과 같습니다:

setNumber(number + 5): 숫자는 0이므로 setNumber(0 + 5). React는 대기열에 "5로 대체"를 추가합니다.
setNumber(n => n + 1): n => n + 1은 업데이터 함수입니다. React는 해당 함수를 대기열에 추가합니다.
다음 렌더링 중에 React는 상태 큐를 통과합니다:

React는 6을 최종 결과로 저장하고 useState에서 반환합니다.
setState(x)가 실제로는 setState(n => x)처럼 작동하지만 n이 사용되지 않는다는 것을 눈치채셨을 것입니다!

 

업데이트 후 상태를 바꾸면 어떻게 되나요? 

한 가지 더 예시를 들어보겠습니다. 다음 렌더링에서는 숫자가 어떻게 될까요?

<button onClick={() => {
  setNumber(숫자 + 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>
    </>
  )
}

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

setNumber(number + 5): 숫자는 0이므로 setNumber(0 + 5). React는 대기열에 "5로 대체"를 추가합니다.
setNumber(n => n + 1): n => n + 1은 업데이터 함수입니다. React는 해당 함수를 큐에 추가합니다.
setNumber(42): React가 "42로 바꾸기"를 대기열에 추가합니다.
다음 렌더링 중에 React는 상태 큐를 통과합니다:

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

요약하자면, setNumber 상태 세터에 무엇을 전달할지 다음과 같이 생각할 수 있습니다:

업데이터 함수(예: n => n + 1)가 대기열에 추가됩니다.
다른 값(예: 숫자 5)은 큐에 "5로 바꾸기"를 추가하고 이미 큐에 대기 중인 항목은 무시합니다.
이벤트 핸들러가 완료되면 React는 다시 렌더링을 트리거합니다. 다시 렌더링하는 동안 React는 대기열을 처리합니다. 업데이터 함수는 렌더링 중에 실행되므로 업데이터 함수는 순수해야 하며 결과만 반환해야 합니다. 내부에서 상태를 설정하거나 다른 부작용을 실행하지 마세요. 엄격 모드에서 React는 각 업데이터 함수를 두 번 실행(두 번째 결과는 버림)하여 실수를 찾을 수 있도록 도와줍니다.

 

명명 규칙 

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

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

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

 

 

요약

  • state를 설정해도 기존 렌더링의 변수는 변경되지 않지만 새로운 렌더링을 요청합니다.
  • React는 이벤트 핸들러가 실행을 마친 후 상태 업데이트를 처리합니다. 이를 일괄 처리라고 합니다.
  • 하나의 이벤트에서 일부 상태를 여러 번 업데이트하려면 setNumber(n => n + 1) 업데이터 함수를 사용할 수 있습니다.

 

 

과제 1 / 2: 요청 카운터 수정 

사용자가 동시에 여러 개의 미술품 주문을 제출할 수 있는 아트 마켓플레이스 앱에서 작업하고 있습니다. 사용자가 '구매' 버튼을 누를 때마다 '보류 중' 카운터가 1씩 증가해야 합니다. 3초 후에는 "보류 중" 카운터가 감소하고 "완료됨" 카운터가 증가해야 합니다.

그러나 "보류 중" 카운터는 의도한 대로 작동하지 않습니다. "구매"를 누르면 -1로 감소합니다(이는 불가능해야 합니다!). 그리고 빠르게 두 번 클릭하면 두 카운터가 모두 예측할 수 없이 작동하는 것처럼 보입니다.

왜 이런 일이 발생하나요? 두 카운터를 모두 수정하세요.

https://codesandbox.io/s/3juca-lwrk1m?file=/src/QueueingSeriesStateUpdates/FixARequestCounter.js

 

 

과제 2 / 2: 상태 큐를 직접 구현하기

이 챌린지에서는 React의 아주 작은 부분을 처음부터 다시 구현해봅니다! 생각보다 어렵지 않습니다.

샌드박스 미리보기를 스크롤하세요. 네 개의 테스트 케이스가 표시됩니다. 이 페이지의 앞부분에서 보았던 예제에 해당합니다. 여러분의 임무는 각 케이스에 대해 올바른 결과를 반환하도록 getFinalState 함수를 구현하는 것입니다. 올바르게 구현하면 네 가지 테스트를 모두 통과해야 합니다.

두 개의 인수가 주어집니다. baseState는 초기 상태(예: 0)이고, queue는 숫자(예: 5)와 업데이터 함수(예: n => n + 1)가 추가된 순서대로 혼합된 배열입니다.

여러분의 임무는 이 페이지의 표에 표시된 것처럼 최종 상태를 반환하는 것입니다!

 

https://codesandbox.io/s/3juca-lwrk1m?file=/src/QueueingSeriesStateUpdates/ImplementTheStateQueueYourself.js

 

발표요약)

1. 이전 대기열의 상태값을 보장하는 값을 사용하려면 setState(prev=> prev+1) 이런식으로 이전상태값을 가져와서 쓴다.
2. 대기열의 이전상태값 명칭은 보통 state 변수명의 앞글자를 쓰는것이 관례. (나는 prev씀)
3. 이런식으로 setState 안에서 멀티라인 계산을 할수도있다. 반환값(set할 값)을 반드시 적어줄것

setTest((test) => {
  let newTest = test + 2;
  console.log("현재 대기열의 test", test);
  console.log("뉴 test", newTest);
  return newTest;
});

 

반응형