티스토리 뷰
React
:React - React Concurrent Mode
| React Concurrent Mode?
최근 리액트 16.8이 릴리즈 되고 React hook이 정식으로 포함 되면서 React Hook이 매우 핫한데요, 리액트팀의 React Hook 다음 로드맵인 react의 concurrent mode에 대해서 미리 살펴보고자, 사내 스터디에서 관련 자료를 조사하고 정리했던 내용을 블로그에 정리해 보았습니다:)
아직 정식으로 발표 된 내용이 많이 없어 관련 자료를 조사하며, 의역이 들어간 부분이 있을거라 참고하고 보시고 틀린 부분은 피드백 주시면 될 것 같습니다.! 그리고, React Concurrent mode와 관련해 아직은 정식으로 확정된 내용이 많이 없어, 변경될 가능성이 충분하니 참고만 하시고 읽으시길 바랍니다 :)
리액트 로드맵 : https://reactjs.org/blog/2018/11/27/react-16-roadmap.html
Concurrent React는 많은 작업들을 한번에, 그들의 우선순위를 바꿔 가면서 동작하게 할 수 있게 한다.
-Concurrent Rendering in React - Andrew Clark - React Conf 2018 -
| 시작하기에 앞서
우선 react concurrent mode에 대한 언급은 2018년 4월경 아이슬란드에서 있었던 jsconf 에서의 Dan Abramov의
먼저 Concurrent 라는 단어는 병행성 이라는 의미를 가지고 있습니다.
React의 Concurrent mode는 초기에는async mode
라고 종종 불리곤 했었는데, 추후에 리액트팀에서는 리액트가 가지는 다른 우선순위 (priority of dom update, priorty of api) 에서의 task들에 대한 리액트의 수행능력을 강조하기 위해서, concurrent mode라고 이름을 바꾸었습니다.
" Concurrent React는 많은 작업을 한번에, 그들의 우선순위를 바꿔 가면서 동작하게 할 수 있게 한다." 라고 위에서 Andrew Clark 이 말한 내용을 곱씹어 보면 왜 Concurrent 라는 단어를 선택했는지도 엿볼수 있게 됩니다.
> 핵심
우선 react concurrent mode의 지향점을 먼저 살펴보면 아래와 같습니다.
Concurrent React
는 위와 같은 특징들 덕분에 유저의 디바이스와 네트워크를 리액트 앱에 적응 시키고, 빠른 인터렉션인 상황에는 즉각적인 느낌을 주고, 느린 인터렉션은 그럴만한 이유가 있다는 느낌을 주게 합니다. (adapt to the user's device and network, allowing for fast interactions to feel instant while slower interactions feel responsive)
> 리액트 공식문서에 소개된 react concurrent mode
위의 링크에 들어가면 리액트 팀의 공식 사이트에 로드맵상 react concurrent mode에 대해 설명한 내용을 확인 할 수 있는데요, 그중 일부를 발췌해 아래에 적은 내용입니다.
Concurrent Mode
는 리액트 앱이 조금더 메인 thread에 blocking 없이 컴포넌트 tree상에서 rendering 하는데에 책임을 가집니다. cpu관점에서는 리액트를 긴 시간동안 render 할때, 높은 우선순위의 이벤트들을 dom update의 우선순위를 핸들링하는데에 끼어들수 있도록 허락해줍니다.그리고 i/o관점에서는 Concurrent mode는 또한 Suspense라는 feature를 통해 빠른 연결을 통해 불필요한 로딩을 skip하고 사용자 경험을 증대시킵니다.
Concurrent 리액트는 Time Slicing, 그리고 React Suspense 같은 feature들을 포함하고 있습니다. scheduler 그리고 react-cache 같은 새로운 라이브러리 들도 소개 되고 있습니다.
Concurrent mode
는 리액트의 비전 중 큰 부분을 차지합니다. CPU-영역의 작업에서는, non-blocking rendering을 허가하고, application을 복잡한 구조의 컴포넌트 트리에서 책임을 갖게 합니다. IO-영역에서는, Concurrent mode는 Suspense를 더 좋게 만듭니다. 만약 네트워크가 충분히 빠르다면, 로딩 인디케이터를 보여주는것을 피하게 합니다. 또한 Concurrent mode는 협력적인 main thread scheduler에 의존하고 있고, 리액트팀은 크롬 팀과 이러한 기능들을 브라우저에게 옮기기 위해 협업하고 있습니다.
| Time slicing and Suspense
우선 React concurrent mode는 Time slicing (CPU bound) 그리고 Suspense (IO bound) 이 두가지의 feature로 나뉘게 됩니다.
CPU Bound 는 프로세스가 진행되는 속도가 CPU 속도에 의해 제한되는 것을 의미합니다.
I / O Bound 는 프로세스가 진행되는 속도가 I / O 하위 시스템의 속도에 의해 제한됨을 의미합니다.
CPU bound와 IO bound에 대한 설명은 아래 첨부한 링크를 참고하면 더 쉽게 알수 있을것입니다.
https://stackoverflow.com/questions/868568/what-do-the-terms-cpu-bound-and-i-o-bound-mean
결론적으로 Concurrent React에서의 Time Slicing 그리고 Suspense 의 특징은 아래 첨부한 React conf 발표 중 캡쳐한 사진 에서 살펴볼수 있는데요, 뒤에 그 각각의 특징들을 더 자세히 살펴 보겠습니다.
| Time Slicing
Time Slicing 기법은 rendering 프로세스가 진행되는 속도가 CPU 속도에 의해 제한되는 상황일때 높은 우선순위의 업데이트가 낮은 우선순위의 업데이트에 의해 block 되지 않는 일반적인 기법입니다.
Time Slicing is a generic way to ensure that high-priority updates don’t get blocked by a low-priority update. Problems it solves: When rendering is CPU-bound.
High Priority: Updates involving user input (e.g. text inputs)
Low Priority: Updates involving data loading or expensive calculation
우선 Time Slicing 기법이 적용 될수 있는건 react 16으로 업데이트 되면서 성능이 최적화 될수 있었던 큰 특징인 React Fiber라는 context안에서 돈다는 점 덕분인데요, 요약 해서 말하자면 컴포넌트 업데이트가 되는 과정을 Render Reconciliation, Commit 두가지 phase 로 분리하면서 Interruptible한 시점이 생기고, 이 과정 속에서 우선순위를 핸들링 할수 있게 되는 것입니다.
(React Fiber 관련 링크 https://tech.wanted.co.kr/frontend/2018/01/07/react-fiber.html )
아래는 react concuurent 에 대한 내용이 잘 정리 되어 있는 https://github.com/sw-yx/fresh-concurrent-react/blob/master/apis/time-slicing.md 에서 발췌한 time slicing 관련 내용입니다.
Time slicing을 통해, 리액트는 이제 여러개의 frame으로 퍼져나가는 렌더링 작업들, idle callback등의 작업이 이뤄지는 동안 자식 컴포넌트들의 업데이트 계산을 chunk로 나누어서 하였다. 이것은 느린 device들의 UI responsiveness 를 향상 시켰다. Time slice는 모든 어려운 cpu 스케쥴링 작업을 개발자가 신경쓸 필요없이 훌륭한 작업을 해낸다.
> Time slicing 의 특징
리액트는 렌더링 되는 동안 thread를 block하지 않는다.
디바이스가 빠르면 synchronous하게 작동하는것 처럼 느끼게 한다.
디바이스가 느리면 그럴만한 이유가 있는것 처럼 느끼게 한다.
오직, 마지막으로 렌더링 된 상태만 display 된다.
> Time slicing 관련 Demo
Time slicing과 관련한 Demo는 Dan abramov가 js conf 에서 발표한 Beyond React 16 영상에서 가장 잘 확인 할 수 있는데요,(https://www.youtube.com/watch?v=nLF0n9SACd4 3분 부터 시작), 아래 관련 예제 코드를 첨부하긴 하지만 그렇게 긴 시간은 아니니, 영상을 보는것을 추천 드립니다.
https://codesandbox.io/s/xl3rjr9mjo 이 링크를 들어가면 관련 코드와 예제를 보실 수 있고, 예제 자체는 https://timeslicing-unstable-demo.surge.sh/ 이 링크로 가시면 확인 할 수 있습니다.
아래는 Dan abramov의 영상 내용을 요약한 내용입니다. (앞에서 말씀 드렸듯 영상을 보는것을 추천드립니다.)
예제에서는 유저가 입력한 인풋값에 따라 그래프상의 노드들이 무한정 늘어나는 예제인데요, 이 때 Synchronous, Debounce, Asynchronous(concurrnet) 세가지 모드를 인풋값을 무한정 늘려나갔을때의 상황을 비교함으로써, synchronous mode일때는 인풋을 늘려나갔을때 렌더링이 blocking (화면 상 빨간색, 노란색으로 보이는 것은 rendering이 blocking 되는 상황을 보여줍니다.) 되면서 한참뒤에 인풋값이 변경된 화면과, 그래프가 업데이트된 화면이 한참 뒤에 반영되고,
이러한 사용자가 입력한 input이 많아지고, 빠르게 input을 늘려나가고 node가 많아진 상황에 rendering blocking을 해결 하기 위해, 차선책으로 debouce를 통해 최종적으로 업데이트 된 화면을 볼수있게 적용할 순 있지만, Computing power가 충분하고, 불필요한 상황 ( debounce가 적용되어 인풋값이 적은 상황에서 값을 입력하면 debounce 때문에 한참뒤에 그래프가 그려지는 걸 기다려야 하는 상황) 이 발생합니다.
최종적으로 Asynchronous mode일때는 input 값의 입력으로 인한 렌더링의 변화가 그래프가 업데이트 되는 렌더링에 큰 영향을 주지 않으면서, 렌더링이 blocking 되지않는 time slicing 기법의 우위를 확인 할수 있습니다. 심지어 computing power 가 낮은 상황에서도 렌더링 blocking이 최적화 된 상황이므로, 유사하게 동작하게 됩니다.
| React Suspense
React Suspense 는 렌더링이 I/O bound일때 cache 에서 데이터를 불러올 때 렌더링을 suspend 하는 방식입니다.
React Suspense is a generic way for components to suspend rendering while they load data from a cache. Problems it solves: When rendering is I/O-bound.
간단히 설명하자면, 빠른 네트워크 상황에서는 굳이 fallback ui를 보여줄 필요가 없으므로 (로딩 인디케이터) ,data를 fetch 하는 동안 컴포넌트를 background에서 렌더링 하고, tree가 모두 준비되면 display하는 방식입니다.
그러나 느린 네트워크 상황에서는 데이터를 불러올때 보여주는 fallback UI (place holder) 를 각각 data를 불러오는 영역(컴포넌트에)에 정확히 배치(precisely control) 함으로써 다른 영역에서의 data fetch 때문에 페이지 전체가 로딩되는 것을 기다리는 것을 방지 하게 되는 것입니다.
It is a fundamentally new capability that lets you render a component tree “in background” while components are fetching data, and display them only after the whole tree is ready.
For slow connections, it gives you full control over where and when to show a placeholder. It doesn’t destroy the previous view while this is happening.
> Suspense 의 특징
data가 준비될때까지 state update를 멈춘다.
add async data to any component without "plumbing"
빠른 네트워크에서는, 모든 tree가 준비 된 후에 render 한다.
느린 네트워크에서는, 정확하게 로딩 스테이트를 control한다.
high-level api와 low-level api가 있다.
> Suspense 관련 demo
이 역시, Dan Abramov의 js conf 영상을 확인하면 쉽게 관련 demo 를 살펴 볼수 있습니다.
( https://www.youtube.com/watch?v=nLF0n9SACd4 13분 24초~)
그리고 Andrew Clark의 2018 React Conf 에서도 역시 관련 demo 영상을 살펴 볼수있습니다.
( https://www.youtube.com/watch?v=ByBPyMBTzM0&t=1305s)
영상에서는 react -cache를 활용해 기존에 data를 받아온 페이지는 빠르게 그 페이지를 보여주고, Suspense에 해당하는Placeholder 컴포넌트에 "maxDuration" prop을 넘겨, 해당하는 duration을 넘겼을때는 느린 네트워크로 감지하여 로딩 인디케이터를 보여주고, 아닐시에는 보여주지 않는 등, 다른 네트워크 상황에 유연하게 대처하고 있는 것을 확인 할 수 있었습니다.
https://github.com/sw-yx/fresh-concurrent-react/blob/master/apis/react-suspense.md 에서 이와 관련한 feature 들을 빠르게 확인 할수 있습니다.
영상에서 확인할 수 있듯이, 네트워크가 충분히 빠른 상황이면 불필요한 로딩 인디케이터를 보여주지 않고, 바로 tree가 준비되는대로 보여주고, (+ cache) 네트워크가 느린 상황에서만 fallback ui (로딩 인디케이터)를 보여주게 됩니다.
아래 gif 파일에서의 latency는 네트워크 상황을 뜻합니다.
latency 1초일때와 3초일때 비교
또한, 각각의 영역에 로딩 관련 인디케이터를 배치 함으로써 하나의 data fetch때문에 전체 페이지가 로딩 되는 듯이 보여주는게 아닌, 먼저 fetch 된 부분은 먼저 보여주고 나중에 data가 fetch 되는 부분은 나중에 보여줌으로써 사용자에게 더 나은 경험을 제공하고 있습니다.
Suspense 관련 DEMO 코드
https://codesandbox.io/s/k3m2rq627o
| 느낀점
아직은 많은 개발자 들이 react hook에 관심을 가지고 있고, react concurrent mode가 정식 릴리즈 되려면 많은 기간이 남아 있긴 하지만, 언젠간 거쳐 가야 할 react concurrent mode에 대해 조사하면서 리액트 팀의 방향성에 대해서 미리 파악해 볼 수 있었고, 또한 크게 느꼈던 점은, React concurrent mode는 사용자의 computing power가 어떻든 (time slicing) , 네트워크 상황이 어떻든 (Suspense) 이에 맞게 유연하게 대처하는 feature들을 추가 함으로써, 이러한 기술적인 발전은 결국엔 사용자의 ux를 고려한 feature들인것 임을 보고, 맹목적인 기술 발전에만 목매는 팀이 아닌것 같다는 점이 정말 감명깊었던 것 같습니다.
| References