React를 공부할 때 중요하게 배우는 개념 중 하나는 상태관리다.
React는 상태를 바탕으로 View를 그리기 때문에 일반 변수로 사용하지 않고 setState
로 상태를 할당한다고 배운다.
하지만 JavaScript를 공부할 때 처럼 console.log
로 상태 변화를 확인하려 하면 이상한 점을 느끼게 된다.
그 이상함(console.log
의 결과값이 이전 상태를 나타냄)은 setState가 비동기적으로 처리되기 때문인데 어째서 React는 상태 변화를 비동기적으로 관리하는지 정리해보겠다.
많이들 하는 실수인데, 상태를 처음 접하면 많이들 아래와 같은 코드로 변경된 상태를 확인하려한다.
import React, { useState } from "react";
function App() {
const [state, setState] = useState(1);
const upState = () => {
setState(state + 1);
console.log(state);
};
return (
<div className="App">
<button onClick={upState}>상태값 UP</button>
<p>상태값은 {state}</p>
</div>
);
}
export default App;
버튼을 누르면 변경한 상태값이 정확히 랜더링 된다.
하지만 이상하게도 console.log(state)
값은 이전 상태값을 출력한다.
이러한 문제 때문에 setState
이후 바로 상태값을 참조하여 다른 작업을 할 때 문제가 생기게 된다.
그리고 그 문제를 해결하기 위해서 react-thunk
, react-saga
등을 사용해 동기화 처리를 해준다.
그렇다면 왜 React는 상태값을 비동기적으로 처리하게 만들었을까?
리액트는 batch update를 16ms 단위로 진행한다.
16ms 동안 변경된 상태 값들을 모아서 단 한번 리랜더링을 진행한다.
이러한 행동은 웹 페이지 랜더링 횟수를 줄여 좀 더 빠른 속도로 동작하게끔 만든다.
아래의 코드로 랜더링 횟수를 확인해 볼 수 있다.
import React, { useEffect, useState } from "react";
function App() {
const [state1, setState1] = useState(1);
const [state2, setState2] = useState(1);
const [state3, setState3] = useState(1);
const [renderCount, setRenderCount] = useState(0);
const upState = () => {
setState1(state1 + 1);
setState2(state2 + 1);
setState3(state3 + 1);
};
useEffect(() => {
setRenderCount(renderCount + 1);
}, [state1, state2, state3]);
return (
<div className="App">
<button onClick={upState}>상태값 UP</button>
<p>상태값1 {state1}</p>
<p>상태값2 {state3}</p>
<p>상태값3 {state3}</p>
<p>랜더링 횟수 {renderCount}</p>
</div>
);
}
export default App;
코드를 실행하여 상태값 UP 버튼을 누르면 각각의 상태들을 1씩 올린다.
상태값을 바꿀 때 마다 리랜더링을 한다면 랜더링 횟수는 늘어나야 하는데 버튼 한 번당 동일하게 1씩 올라간다.
따라서 React는 일정 시간동안 변화한 상태값들을 모아 한 번에 처리하는 것을 알 수 있다.
React의 setState 등이 비동기적으로 작동하는 이유는 일정시간동안 변화하는 상태를 모아 한 번에 랜더링 하기 위해서이다.
참고자료: https://medium.com/swlh/react-state-batch-update-b1b61bd28cd2