https://react.dev/learn/queueing-a-series-of-state-updates를 읽고 정리한다.

 

state 변수를 설정하면 다른 render를 대기열에 추가한다.

때때로 다음 render를 대기열에 추가하기 전에 많은 연산을 수행하고 싶을 수도 있다. 이때 리액트가 state update를 일괄 처리하는 방법을 이해하는 것이 도움이 될 것이다.

 

리액트는 state update를 일괄 처리한다.

각 render의 state 값을 고정되어있다. 그래서 아래와 같이 state setter 함수를 여러 번 호출을 하더라도 현재 상태 값은 변하지 않는다.

 

리액트는 이벤트 핸들러 내 모든 코드가 실행될 때까지 기다린 후 state update가 처리된다. 이는 리렌더가 모든 setNumber() 호출 후에  re-render가 일어나는 이유이다.

 

이는 레스토랑에서 웨이터가 주문을 받을 때를 떠올려보자. 웨이터는 첫 번째 요리를 언급할 때 바로 주방에 가지 않는다. 대신, 주문이 끝나고 이를 변경할 수 있으며 테이블에 다른 사람의 주문도 받을 수 있다.

 

이렇게 하면 아주 많은 re-render를 트리거하지 않고, 여러 컴포넌트의 여러 state 변수를 업데이트할 수 있다. 그러나 이는 또한 이벤트 핸들러와 그 안의 모든 코드가 완료될 때까지 UI를 업데이트할 수 없다는 것을 의미한다. batching(일괄 처리)라고 알려진 이 행동은 리액트 앱을 더 빠르게 만든다. 이는 또한 일부 변수만 업데이트되는 혼란스러운 "중도-멈춤" 렌더를 다루는 것을 피한다.

 

리액트는 클릭과 같은 다양한 의도적인 이벤트를 일괄 처리하지 않는다. - 각 클릭은 별도로 다뤄진다. 나머지는 리액트가 일반적으로 안전한 경우에만 일괄 처리를 수행하므로 안심해라. 예를 들어, 첫 번째 버튼을 클릭이 form을 비활성화하면, 두 번째 클릭은 다시 제출할 수 없다는 것을 확실히 한다.

 

다음 render 전에 동일한 state 변수를 여러 번 업데이트

이는 자주 사용되는 방법은 아니지만, 아래의 방법으로 next render 전에 같은 state 변수를 여러 번 업데이트할 수 있다.

setNumber(n => n + 1)

  • 큐의 이전 값을 기준으로 next state를 계산하는 함수를 전달
  • 이는 리액트에게 state를 단지 교체(replace)하는 대신에 "state 값으로 무언가를 수행하라"라고 말한다.
    • 교체 : setNumber(number + 1)

n => n + 1updater function이라고 불린다. 이 함수를 state setter에 전달하는 시점은 아래와 같다.

  1. 리액트는 이벤트 핸들러에 있는 모든 코드가 실행된 후에 처리되기 위해 이 함수를 대기열에 추가한다.
  2. next render 동안, 리액트는 대기열을 거치고 최종 업데이트된 state를 제공한다.
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은 함수이니까 리액트는 이 함수를 대기열에 추가한다.

next render 동안 useState를 호출하면, 리액트는 대기열을 거친다. 이전의 number state가 0이고, 이는 첫 번째 updater function의 n 인수로 전달한다. 그 후 리액트는 이전 updater function의 반환 값을 다음 updater의 n으로 전달한다.

queued update n returns
n => n + 1 0 0 + 1 = 1
n => n + 1 1 1 + 1 = 2
n => n + 1 2 2 + 1 = 3

 

리액트는 마지막 결과로 3을 저장하고 useState로부터 이를 반환한다.

이것이 예제에서 "+3" 버튼을 클릭했을 때 올바르게 3의 값만큼 증가된 이유이다.

state를 교체한 후 state를 업데이트하면 어떻게 될까

아래 이벤트 핸들러를 가질 때 next render에서 number는 어떻게 될까?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>

이 이벤트 핸들러는 리액트에서 아래처럼 동작한다.

  1. setNumber(number + 5) : number는 0이므로 setNumber(0 + 5)가 된다. 리액트는 대기열에 "5로 교체"를 추가한다.
  2. setNumber(n => n + 1) : n => n + 1은 updater function이다. 리액트는 대기열에 이 함수를 추가한다.

next render 동안, 리액트는 아래의 상태 대기열을 거쳐간다.

queued update n returns
"5로 교체" 0 (unused) 5
n => n + 1 5 5 + 1 = 6

리액트는 최종 결과로 6을 저장하고 useState로부터 리턴한다.

setState(x)는 실제로 setState(n => x)로 동작하지만, n이 사용되지 않는다.

state가 업데이트된 후 state를 교체하면 어떻게 될까

아래 이벤트 핸들러를 가질 때 next render에서 number는 어떻게 될까?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);
}}>

이 이벤트 핸들러는 리액트에서 아래처럼 동작한다.

  1. setNumber(number + 5) : number는 0이므로 setNumber(0 + 5)가 된다. 리액트는 대기열에 "5로 교체"를 추가한다.
  2. setNumber(n => n + 1) : n => n + 1은 updater function이다. 리액트는 대기열에 이 함수를 추가한다.
  3. setNumber(42) : 리액트는 대기열에 "42로 교체"를 추가한다.

next render 동안, 리액트는 아래의 상태 대기열을 거쳐간다.

queued update n returns
"5로 교체" 0 (unused) 5
n => n + 1 5 5 + 1 = 6
"42로 교체" 6 (unused) 42

리액트는 최종 결과로 42를 저장하고 useState로부터 리턴한다.

 

정리하면, setNumber state setter에 전달되는 것이 무엇인지에 따라 다르게 생각해야 한다.

  • updater function(n => n + 1) : 대기열에 추가된다.
  • 그 외 다른 값(5)은 대기열에 "5로 교체"가 추가되며, 이미 대기열 있던 것은 무시된다.

이벤트 핸들러가 실행이 완료된 후, 리액트는 re-render를 트리거한다. re-render 동안, 리액트는 대기열을 처리할 것이다. updater function은 rendering동안 실행하며, 이 함수는 pure 해야 하며 오직 결과만 리턴해야 한다.

  • 내부에서 set state를 하지 마라
  • 다른 side effect를 실행하지 마라

Strict Mode에서 리액트는 실수를 찾는데 도움을 주기 위해 각 updater function 별로 2번 실행할 것이다. 이때 두 번째 결과는 버린다.

 

네이밍 컨벤션

state 변수의 첫 글자로 updater function 인자의 이름을 지정하는 것이 일반적이다.

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

더 긴 코드를 선호한다면, state의 전체 이름은 반복하는 거나, prefix를 사용하는 것이 일반적이다.

setEnabled(enabled => !enabled);
setEnabled(prevEnabled => !prevEnabled)

 

+ Recent posts