useState의 모든것

📅 TIL #156




Intro.


오늘은 useState에 대해서 정말 깊게 다뤄볼 예정이다. 그동안 useState에 대해서 만큼은 정말 열심히 공부했기에 깊이 알고 있다고 생각했지만, 반 정도만 알고 반은 모르는 수준이였다는 것을.. 공부를 더 하고 나서야 깨달았다….


오늘은 정말 제목 그대로 useState의 모든 것을 파헤칠 생각이다.






useState


useState는 기본적으로 React Hook에서 제공하는 함수이며, state를 관리할 때 용이하게 사용한다. 오늘은 단순히 useState의 사용법을 블로깅하고자 하는 것이 아니라 내부적으로 어떻게 되어 있는지, 그래서 어떻게 작동하는지 알아본다.


먼저 useState의 호출 시점은 렌더링 이후이다.


상태 변화가 일어나면 렌더링 시작 - useState 호출 - 렌더링 끝 - useEffect - …(생략)
순으로 React 내부에서 이런식으로 작동한다.


// App.jsx
const App = () => {
  console.log("render-start");

  return (
    <>
      <MyComponent />
    </>
}

// MyComponent.jsx
const MyComponent = () => {
  const [state, setState] = useState(0);

  console.log("initState", state);

  const handleState = () => {
    setState(prev => prev + 1);
    console.log("newState", state);
  };

  console.log("render-end");

  return (
    <div>
      <button onClick={handleState}>Hello</button>
    </div>
  );
};

// 첫 렌더링
"render-start"
"initState" 0
"render-end"

// 버튼 클릭 후 (상태 변화 후) 렌더링
"newState" 0
"initState" 1
"render-end"


버튼 클릭 후 렌더링을 보면, setState로 상태값을 변경 시켰지만 newState는 여전히 0이다. 그리고 리렌더링이 될 때 initState가 1이 되고 렌더링이 끝난다.


여기서 오늘 짚어봐야 할 내용은 useState는 상태 관리를 할 때 사용하는 함수라고 하였는데, 그렇다면 어느 시점에서 어떻게 상태 변화가 일어나는가? 이다.


즉, useState어떤 구조로 되어 있으며, 어떻게 변경된 값을 적용시키는지 알아보는 것이 오늘 블로깅의 핵심이다.


그러면 먼저 useState의 내부 원리를 파헤쳐 보자!


먼저 useState는 클로져 형태로, 외부 변수에 참조해서 새로운 상태 값을 반환하는 원리로 되어있다.


let _value;

export const useState = (initValue) => {
  if (_value === undefined) {
    _value = initValue;
  }

  const setState = (newValue) => {
    _value = newValue;
  };

  return [_value, setState];
};


만약 useState함수를 호출했다면 위의 로직대로 함수가 실행되고 아래 리턴값처럼 값을 반환할 것이다.


useState 내부에서 상태값을 새로운 상태값으로 변경시켜 주는 것은 맞지만, 변경만 시켜줄 뿐 그 이상은 하지 않는다.


그렇다면 변경된 값은 어떻게 되는 것일까? 변경된 값을 가져오는 시점은 결국 컴포넌트 렌더링 이후 useState가 호출되는 시점이 변경된 값을 가져오는 시점이라고 할 수 있다.


다시 아까 두번째 콘솔로그에 찍혀있던 값을 살펴보자.

// 버튼 클릭 후 (상태 변화 후) 렌더링
"newState" 0
"initState" 1
"render-end"


렌더링 이후에 useState가 호출되기 전에는 값이 그대로이다. 하지만 useState가 호출되고 난 이후에 초기값이 변경되었고, 그 다음 렌더링이 끝났다.


이 부분을 조금 더 활용한 방법이 위에서 사용한 코드와 같다.


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

const handleState = () => {
  // prev에는 상태가 변경되기 전의 값이 저장되어 있음
  setState((prev) => prev + 1);
};


결론은 setState를 호출해서 상태값을 변경해줄 수는 있지만, 렌더링 이후 useState가 호출되어야만 변경된 상태값을 적용시킬 수 있다는 것을 알 수 있었다.




useState는 비동기


이제 막 React Hook을 공부하면 useState가 비동기인 것이 생소할 수 있다. 하지만 사실 앞서 설명했던 모든 내용들이 다 비동기로 작동하기 때문에 가능했던 로직들이다.


만약 useState가 동기적으로 작동했다면 상태1 변경 후 상태 2를 변경하기 시작할 것이다. 이러면 비효율적이기에 더 나은 성능을 위해 비동기적으로 작동한다고 생각하면 된다.