최근 사내에서 리액트 버전을 17에서 18로 올리게 되었다.
리액트 18 버전은 일년전 이맘때쯤 짜잔하고 등장하였는데 개인 공부로 문서를 읽어본적은 있어도 제대로 다뤄본적은 없는 것 같아 더 늦어지기전에 하나씩 조금 더 들여다보고 최대한 모든 기능을 활용해보려 한다.
우선 첫번째 스타트는 React 18의 automatic batching (자동 배칭)이다!
자동배칭으로 바로 다이빙하기 이전에 간단하게 배칭과 관련된 state에 대한 이야기도 조금 해보면 좋을 것 같다.
아래 코드에서 alert은 어떤값을 보여줄까?
정답은 0이다.그렇다면 왜 1이 아닌 0을 보여주는것일까?
바로 setState가 리액트내부에서 비동기적으로 처리되기 때문이다.
그렇다면 또 다시 꼬리 질문으로 왜 setState가 비동기적으로 처리가 되는지에 대한 의문이 든다 🤔
이에 대한 답변으로 2017년도에 react 이슈로( RFClarification: why is setState
asynchronous? ) 비슷한 질문이 올라온적이 있다.
이에 대해 2018년도에 올라온 Dan의 답변을 요약해보자면 아래와 같은 이유로 setState는 비동기로 작용한다고 한다.
setState()
를 동기적으로 업데이트 하여 매번 리렌더링하는 것은 비효율적이다.물론 질문자는 해당 상황에 대해 인지하고 setState()
는 reconciliation이 끝날때까지 기다리는것이 아니라 동기적으로 변경해주면서 rendering
은 비동기적으로 만들 수는 없는지에 대해서도 언급하였다.
이에 대해 Dan은 명확한 답은 없다고 하지만 생각할 수 있는 이유들 몇가지를 던져주었다.
내부 일관성 보장 (Guaranteeing Internal Consistency)
동시 업데이트 활성화 (Enabling Concurrent Updates)
One way we’ve been explaining “async rendering” is that React could assign different priorities to
setState()
calls depending on where they’re coming from: an event handler, a network response, an animation, etc
이러한 이유로 인하여 setState()
는 비동기적으로 작용하고 있는데 위에서 계속해서 나왔던 배칭이 무엇인지 조금 더 알아보고 오늘 글의 주제인 React 18 automatic batching과 무슨 차이가 있는지 알아보는 시간을 가져보자
리액트는 기본적으로 batching을 활용한다. 배칭이 무엇인가 하면
batching (배칭)이란, 이벤트 핸들러나 hooks안에서 상태 업데이트를 묶어서 동작하도록 만들어준다. 즉, 렌더링이 한번만 일어나게 해준다
리액트 17에서도 batching은 존재하였지만 지원되는 범위가 리액트 18가 달랐다.
예시와 함께 조금 더 살펴보도록 하자
현재 리액트는 버전 17이며 아이스크림 버튼에는 아래 코드의 handleIceCreamClick
핸들러가 연결되어있다.
이때 버튼을 클릭하게 된다면 콘솔창에 "Component Rendering 🚀"
과 useEffect가 불렸습니다 | 오빠: ${brother} 동생: ${sister}
몇번씩 찍히게 될까?
리액트 17버전에서도 리액트 이벤트 핸들러 내부에서는 배칭이 존재했기에 버튼 한번 클릭시 한번씩만 찍히는것을 볼 수 있다.
하지만 코드를 아래처럼 수정한다면?
async await을 활용하여 handleIceCreamClick
을 항상 프라미스를 반환하는 함수로 변경하고 해당 함수내에서 setState를 실행해보았다.
이 경우에는 리액트 17버전에서는 배칭이 적용되지 않아 렌더링이 버튼 클릭한번당 setState의 갯수(setBrother, setSister)만큼 2번씩 발생하는 것을 볼 수 있다.
그런데 새롭게 버전업이 된 react 18에서는 automatic batching으로 인해 react 17에서 보던 배칭과는 다른점을 볼 수 있다.
automatic batching, 자동 배칭이란, 말 그대로 모든 업데이트가 자동으로 배칭됨을 의미한다. 리액트 이벤트 핸들러 내부뿐만 아니라 promises, setTimeout, native event handlers들 안에서도 배칭이 적용된다.
그런데 여기에는 조건이 존재한다.
createRoot
를 사용했을때만 automatic batching이 적용된다. render
를 사용하게 되면 리액트 18버전이어도 automatic batching이 적용되지 않는다! 기존 react 17에서도 리액트 이벤트 핸들러 내부에서는 배칭이 적용되었기에 위에서 사용했던 promise 예제만을 react 18버전에서 확인해본다면 아래와같이 렌더링이 버튼클릭당 한번씩만 되는 것을 볼 수 있다.
배칭을 강제적으로 하지 않게 만드는것은 흔하게 발생해서는 안되는 일이지만 일부 코드는 상태 변경 직후에 DOM에서 무언가를 읽어야 할 수도 있을 것이다.
이러한 경우에는 ReactDOM.flushSync()
를 사용하여 배칭(일괄 처리)를 선택적으로 하지 않을수도 있다.
위와 같은 경우에는 아이스크림 버튼을 클릭했을 때 배칭이 적용되지 않아 flushSync
후에 렌더링이 한번 진행되고 두번째 flushSync
후에 또 다시 렌더링이 진행되는것을 볼 수 있다.
추가적으로 약간의 호기심이 생겨서 flushSync
안에 여러개의 setState
를 넣어보았는데 처음에는 flushSync안에 setState 갯수 만큼 렌더링이 일어날까? 라는 의문점을 갖고 있었다.
하지만 해당 부분은 flushSync안의 setState 갯수가 아닌 flushSync
가 하나의 렌더링으로 처리된다는 것을 알게 되었다!
배칭에 대해서는 익히 들어왔는데 배칭과 오토 배칭에 차이에 대해서는 이번 기회를 통해 정확히 알게 되었다. 이 글을 읽게되는 모두가 배칭과, 오토 배칭 그리고 그 차이에 대해 조금이나마 알아가는 시간이었으면 한다.
틀린 내용이 있다면 언제든 댓글 달아주세요 ☺️
리액트 18 버전만 썼어서 몰랐는데, 17 버전 대비 배칭에 큰 차이가 있었군요! 좋은 글 감사합니다!