(번역) 일련의 State Update를 큐에 저장하기 - NEW 리액트 공식문서

hongregii·2023년 3월 6일
0

staet 업데이트는 배치

복습 : 각 렌더링의 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 + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

버튼 누르면 setState() 세번 실행해도 → 0 에서 1로 바뀐다.

사실상 이것과 같다 :

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

리액트는 이벤트 핸들러 안에 있는 모든 코드가 실행되길 기다린다.

위 현상이 setState()가 몇번 불려도 state의 스냅샷을 가지고 가기 때문이라는 사실 이외에도 한가지 개념을 더 알아야 한다.

이벤트 핸들러 함수안의 모든 코드가 다 실행되고 난 뒤에야 리액트가 상태를 업데이트시킨다.

그래서 setNumber() 세 개가 다 실행되고 나서야 리렌더링이 일어난다.

비유 : 레스토랑에서 주문받기

웨이터는 주문을 다 받고, 손님이 바꿀 것 다 바꾸고, 다른 테이블 오더도 받고 나서 주방으로 움직인다.

이리하야 여러 state 값을 업데이트 할 때-심지어 다수의 컴포넌트의 state값들도- 너무 많은 리렌더링을 하지 않을 수 있는 것이다.

이 말은 이벤트 핸들러 안의 모든 코드가 실행 완료 될 때까지 UI가 업데이트 되지 않는다는 뜻이기도 하다.

이것이 바로 Batching. 리액트 앱 속도를 빠르게 만들어준다. 변수 중 일부만을 업데이트하는 렌더링을 방지하기도 함.

리액트는 다수의 이벤트를 묶어서 Batch 하지 않는다.

클릭을 여러번 한다고 하여 여러 클릭 간 state 업데이트를 batch (묶기) 하지 않음. 각 클릭은 따로 처리된다.

따라서 버튼을 눌렀을 때 disabled 로 만드는 함수를 넣었다면, 두번째로 누르면 버튼이 작동하지 않을 것이다!

다음 렌더링 전까지 한 state를 N번 업데이트하기

자주 안 나오는 사례이긴 함. 그러나 위의 예시를 실제로 하고 싶다면, setNumber(number + 1) 처럼 바뀌기 원하는 다음 state값 을 즉시 setState의 인자로 넘기지 마라 (=replace).

이렇게 하면 된다!

기존 state값을 가지고 다음 state를 만들어주는 함수 를 인자로 넘겨줘라.

setNumber(n => n+1)

이건 리액트에게 즉시 새로운 값을 state 자리에 넣는게 아니라 "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>
    </>
  )
}

n => n+1updater 함수 라고 불린다. 이것을 setState()의 인자로 넣으면 :

  1. 이벤트 핸들러의 모든 코드가 실행된 뒤에 리액트는 이 함수가 처리되도록 큐 한다.
  2. 다음 렌더 중, 리액트는 큐를 쭉 실행시키고 최종 업데이트 된 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은 함수다. 리액트가 이 함수를 큐에 올려놓는다.

useState 를 다음 렌더에서 호출하면, 리액트는 큐를 쭉 실행시킴. 이전 number state값이 0이었기 때문에, 리액트는 이 값 0을 첫번째 updater 함수n 인자로 넘겨준다. 그 뒤 리액트는 이전 updater 함수의 리턴값을 다음 updater 함수n 인자로 넘겨준다. 더보기..

그래서 아까와 달리, 위 코드를 실행하면 제대로 03 으로 바뀐다.


그럼 replace 이후에 update function 넣으면?

이렇게 하면 어떻게 될까? number state가 다음에 무슨 값이 될 것이라고 생각하는가?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>
  • 처음에는 인자에 그대로 다음 값 집어넣었음 (replace)
  • 그 이후에 즉시 updater 함수 넣었음

=> 리액트는 이렇게 한다.

  1. setNumber(number+5) : 현재 number0이라서 setNumber(0+5)다. 리액트는 큐에 "5로 replace"를 추가한다.

  2. setNumber(n => n+1) : n => n+1updater 함수. 리액트는 그 함수 를 큐에 추가.

다음 렌더링 시, 리액트는 state 큐를 실행한다...

que된 업데이트n리턴값
5로 바꿔줘(replace)0 (사용 안됨)5
n=>n+155 + 1 = 6

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

setState(x) (replace 하기) 는 사실상 setState(n => x) 이렇게 작동하지만, n은 사용 안된다는 사실!


반대로 update 이후에 replace 하면?

이 경우는 어떨까?

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);
}}>
  1. setNumber(number+5) : 현재 number0이라서 setNumber(0+5)다. 리액트는 큐에 "5로 replace"를 추가한다.

  2. setNumber(n => n+1) : n => n+1updater 함수. 리액트는 그 함수 를 큐에 추가.

(여기까지는 똑같다)

  1. setNumber(42) : 리액트는 "42로 replace" 를 큐에 추가.

다음 렌더에 리액트는 state 큐를 실행한다...

que된 업데이트n리턴값
5로 바꿔줘(replace)0 (사용 안됨)5
n => n + 155 + 1 = 6
"`42로 replace"6 (사용 안됨)42

이제 리액트는 42를 최종 결과로 저장, useState에서 리턴함.

즉, setNumber state setter에 어떤 인자를 넘겨줄지는 이렇게 생각하면 된다.

  • updater 함수 (ex. n => n+1)이 큐에 추가됨
  • updater 함수 외 모든 값들 (ex. 5) 는 "5로 replace"를 큐에 추가. 기존 큐는 무시된다!!

명명규칙 Naming Conventions

setState() 인자에도 네이밍 컨벤션이 있는줄 몰랐다. 디테일하군..

보통 updater 함수의 인자는 해당 state 이름의 알파벳 이니셜 따라가는 경우가 많음!

// [enabled, setEnabled]
setEnabled(e => !e);

// [lastName, setLastName]
setLastName(ln => ln.reverse());

// [friendCount, setFriendCount]
setFriendCount(fc => fc * 2);

조금 더 자세한 규칙은 그냥 state 이름을 풀네임으로 쓰기.
setEnabled(enabled => !enabled)

또는 접두사 prev 사용
setEnabled(prevEnabled => !prevEnabled)


세줄 요약

  • setState는 기존 렌더의 변수값을 바꾸지 않음. 대신 새로운 렌더링을 요청함. (state = const)

  • 리액트는 이벤트 핸들러가 전부 실행 완료되고 나서 state 업데이트를 실행함.

  • 한 이벤트에서 여러번 state를 바꾸고 싶다면, setNumber(n => n + 1) 처럼 updater 함수 를 사용할 것.

profile
잡식성 누렁이 개발자

0개의 댓글