useState 구현

왜 일반 변수가 아닌 useState로 만든 변수를 사용해야 하는가?

  1. 렌더링을 발생시키기 위해
  2. 상태를 유지하기 위해

아래와 같이 일반 변수를 사용하면

function Counter() {
  let state = 0;

  function handleButtonClick() {
    state += 1;
  }

  return (
    <>
      <div>{state}</div>
      <button type="button" onClick={handleButtonClick}>
        클릭
      </button>
    </>
  );
}

버튼을 클릭할 때마다 state는 1씩 증가하나, 리렌더링은 일으키지 않는다.

function Counter() {
  const [, triggerRender] = useState();
  let state = 0;

  function handleButtonClick() {
    state += 1;
    triggerRender();
  }

  return (
    <>
      <div>{state}</div>
      <button type="button" onClick={handleButtonClick}>
        클릭
      </button>
    </>
  );
}

리렌더링이 발생하나, 항상 초깃값이 렌더링된다.
렌더링이 발생될 때마다 함수가 다시 새롭게 실행된다.
Counter의 로컬 변수인 state는 새로 선언되고 0으로 초기화된다.

React는 어떻게 상태 업데이트 시, 렌더링을 발생시키면서 상태를 유지할까?
1. 상태를 유지하기 위해 함수형 컴포넌트 상위 스코프에 변수를 선언했을 것이다.
2. setState 내부에 렌더링을 발생시키는 로직이 있을 것이다.

게으른 초기화

(내용 추가)

setState

왜 콜백 함수 내부에서 이전 배열을 받아서 매번 새로 배열을 만드는가?

const [state, setState] = useState([]);

// 왜 state를 바로 수정하지 않는가?
state.push(1);

// 왜 state를 복사하여 수정하지 않고,
const newState = state;
newState.push(1);
setState(newState);

// '이전 배열'을 받아서 '새로운 배열'을 만드는가?
setState(prev => [...prev, 1]);
  1. state를 바로 수정하면 렌더링이 발생하지 않는다.
  2. 새로운 배열을 만드는 이유는 React가 동등 비교를 하는 방식 때문이다.
  3. state로 새로운 배열을 만들지 않고, 콜백 함수의 매개변수로 이전 배열을 받아서 새로운 배열을 만드는 이유는 상태 업데이트의 특성에 대응하기 위해서다.

동등 비교

React에서 동등 비교는 아래와 걑은 순서로 진행된다.
1. Object.is로 비교
2. 객체 간 얕은 비교

따라서 새로운 배열을 생성하지 않고, 이전 배열을 변경하면 React는 상태 변경을 감지할 수 없다.

상태 업데이트

같은 렌더링 시점에 상태를 여러번 업데이트하는 경우, 올바르게 작동하지 않을 수 있다.

function handleClick() {
  setState(state + 1); // setState(0 + 1)
  setState(state + 1); // setState(0 + 1). setState가 실행되어도 현재 실행 중인 코드의 state가 업데이트되진 않는다.
}

콜백 함수를 사용하면, pending state를 기반으로 새로운 상태를 만든다.

function handleClick() {
  setState(prev => prev + 1);
  setState(prev => prev + 1);
}

state가 0이었다면 click 후 2로 변한다.

https://react.dev/reference/react/useState#updating-state-based-on-the-previous-state

0개의 댓글

Powered by GraphCDN, the GraphQL CDN