오늘은 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가 호출되어야만 변경된 상태값을 적용시킬 수 있다는 것을 알 수 있었다.
이제 막 React Hook을 공부하면 useState
가 비동기인 것이 생소할 수 있다. 하지만 사실 앞서 설명했던 모든 내용들이 다 비동기로 작동하기 때문에 가능했던 로직들이다.
만약 useState
가 동기적으로 작동했다면 상태1 변경 후 상태 2를 변경하기 시작할 것이다. 이러면 비효율적이기에 더 나은 성능을 위해 비동기적으로 작동한다고 생각하면 된다.