리액트에서 setState를 통해 상태를 업데이트할 경우, 업데이트된 상태는 즉시 반영되지 않는다. 이유는 setState가 비동기적으로 작동하기 때문이다. 리렌더링이 된 후에야 업데이트된 state가 반영된다.
리액트의 상태를 업데이트하는데 있어, 비동기적으로 작동하는 속성은 여러개의 상태를 다룰 때 퍼포먼스 측면에서 유리하다. 동기적으로 작동했다면 state1 update > state2 update > ... 이런 식으로 하나씩 업데이트가 될 것이다.
여러 상태가 동시다발적으로 업데이트 하는 경우, 리액트는 상태를 batching
하여 업데이트를 진행하게 된다.
배칭은 리액트가 더 나은 성능을 위해 여러개의 상태 업데이트를 하나의 리렌더링으로 묶는 것을 의미한다.
예로 하나의 클릭 이벤트 안에 두개의 상태 업데이트를 가지고 있다면, 리액트는 언제나 이 작업을 배칭하여 하나의 리렌더링으로 만들게 된다.
const [num, setNum] = useState(1);
const plus = () => {
setNum(num + 1);
setNum(num + 1);
setNum(num + 1);
console.log(num) // 1
}
console.log(num) // 2
plus
함수 실행 시 일반적으로 예상하기로는 +3이 되어 렌더링 결과가 4가 될 것 같지만, 결과는 +1이 되어 2가 호출될 것이다. 이런 것을 리액트에선 batching
이라고 한다. 돔 조작의 비용을 최소화하기 위한 결과인 것이다.
setState 함수가 호출되면 리액트는 바로 전달받은 state값을 바꾸는 것이 아닌, 이 전의 리액트 엘리먼트 트리와 전달받은 state가 적용된 엘리먼트 트리를 비교하는 작업을 거치고(diffing) 최종적으로 변경된 부분만 DOM에 적용한다.
setState 함수는 인자로 새로운 state 객체를 인자를 받을 수도 있지만, 이 전 state 값을 기준으로 계산해야만 한다면, 객체 대신 updater 함수를 전달하는 방법이 있다.
updater 함수를 전달하면 updater 함수 안에서 이전 state 값에 접근 할 수 있다.
setState 호출은 일괄적으로 처리되기 때문에 여러 업데이트 사항이 충돌없이 차례대로 반영되도록 한다.
const [num, setNum] = useState(1);
const plus = () => {
setNum((prevState) => prevState + 1);
setNum((prevState) => prevState + 1);
setNum((prevState) => prevState + 1);
}
console.log(num) // 4
이런 식으로 setState 안에 updater 함수를 전달하는 것이다. 이렇게 되면 결과로 2가 아닌 4가 나오는 것을 확인 할 수 있다.
왜 리액트는 상태를 비동기적 로직으로 처리할까?
- 리액트의 setState가 비동기로 작동하는 이유는 일정 시간 동안 변화하는 상태를 모아 한번의 렌더링으로 처리하기 위해서이다.
- 웹 페이지에서의 불필요한 렌더링 횟수를 줄여 좀 더 빠른 속도로 동작하게 만들기 위해서이다.
왜 리액트는 즉시 state를 업데이트 하지 않는걸까?
- props와 state 사이의 일관성을 해칠 수 있으므로 이것은 디버깅하기 어려운 이슈가 생길 수 있기 때문이다.
참고 문서 : https://velog.io/@seongkyun/React%EC%9D%98-setState%EA%B0%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC%EB%90%98%EB%8A%94-%EC%9D%B4%EC%9C%A0
https://taenami.tistory.com/49