실행 결과는 아래 사진과 같다.input 결과input을 입력할 때마다(value가 변할 때마다) boxes의 배경색깔을 모두 계산한다. box의 수가 10000개이기 때문에 연산 시간이 꽤 필요하다. 따라서 input을 입력하면 브라우저에 렉이 걸리게 된다. input을 느리게 입력한다면 괜찮을 수도 있지만, 일반 유저의 타자 속도일 경우 반드시 렌더링 성능 저하를 유발하는 코드이다.(React 문서에서 이 현상을 '렌더링 차단(blocking)'이라 부른다.)
지금까지는 이러한 상황에서 debounce/throttle 기법을 이용해서 해결했다. 하지만 debounce와 throttle이 완벽한 UX를 보장해주지 않는다. (debounce delay가 500ms이면 유저의 의사와 상관없이 500ms를 기다려야한다.)
이러한 상황을 해결하기 위해 나온 것이 React 18의 Concurrent Mode이며, useDeferredValue hook을 이용하여 deferredValue의 값을 지연시킬 수 있다. 이해가 어렵기 때문에 코드를 통해 설명하기로 한다.
useDeferredValue hook을 이용하여 기존 코드에서 단 2줄을 수정하였다. 이제 "value"를 빠른 속도로 타이핑해보자. input에 있는 value는 빠르게 변하지만 deferredValue의 값을 바로 변경되지 않는다. deferredValue와 deferredValue에 의존하는 boxes 연산은 value보다 urgent하지 않기 때문에 value 값만 먼저 업데이트하고 deferredValue 값은 지연하기 때문이다. 즉, deferredValue에 의해 boxes 연산이 진행되는 와중에 value가 업데이트된다면 boxes 렌더링을 interrupt하고 value만 먼저 렌더링할 수 있다.
이제 input tag의 value는 즉각적으로 변하지만, deferredValue와 boxes는 그보다 낮은 빈도수로 업데이트되는 것을 확인할 수 있다. 이제 렌더링 차단은 일어나지 않는다! 테스트를 입력했을 때 value와 deferredValue의 변화
해결 과정 2
배운 내용을 실제 프로젝트에 적용해보기로 한다. Range Input의 경우 마우스나 터치 이벤트로 인해 아주 빠른 속도로 값이 변하는 UI 이다. range input
위의 사진은 range input이 가리키는 '시각'에 따라서 naver map Polyline을 그리는 UI이다.
특정 시각에 따라 naver map Polyline을 그리는 것은 어느 정도의 연산을 필요로하기 때문에 얼마든지 렌더링 차단이 일어날 수 있다. 실제로 range input을 마우스나 터치로 조작하면 화면이 뚝뚝 끊기며 렌더링되는 것을 확인했다.
생략된 세부 로직이 있지만, STATIC_TIME_KEY의 상태가 mutate될 때마다 Polyline을 새로 그리는 것은 동일하다. onChange event에서 staticTime state를 업데이트하지만, 전역 상태 STATIC_TIME을 mutate 하는 것은 deferredStaticTime에 의해 지연되기 때문에 렌더링 차단이 더이상 일어나지 않는다. (복잡한 연산을 해야하는 렌더링은 staticTime 상태에 의해 interrupt 되면서 지연된다.)
의의
지금까지 사용한 debounce, throttle도 렌더링 성능을 향상하는 좋은 기법이지만, 이번 React 18의 interruptible rendering 덕분에 UX가 더욱 향상되었다.
간편한 hook으로 기능이 제공되기 때문에, 기존 로직에 아주 쉽게 적용할 수 있었다.
아직 React 18에 대해 정확하게 알지 못하는 것이 많아서 더 자세히 알아보아야겠다.(transition 등등)