React

React 18 startTransition, selective hydration, Automatic Batching

student513 2021. 12. 18. 21:42

네이버 DEVIEW 2021 심흥운님의 Inside React(동시성을 구현하는 기술)의 요약 및 소감이다.


React18 버전에서 가장 크게 강조된 키워드는 "동시성"이다.

동시성에 대해 알아보기 위해서는 병렬성과 비교하며 알아보는 것이 좋겠다.

동시성과 병렬성은 동시에 사용가능한 코어의 개수로 결정된다.

동시성 싱글코어에서도 작동 동시에 실행되는것처럼 보임 컨텍스트 스위칭 필요 최소 두 개의 논리적 통제 흐름 예) 동영상 재생하면서 메모장 쓰기
병렬성 멀티코어 필요 실제로 동시에 실행됨 최소 한 개의 논리적 통제 흐름 예) GPU를 이용해 이미지 출력

 

동시성이란 2개 이상의 독립적인 작업을 잘게 나누어 동시에 실행되는 것처럼 보이도록 프로그램을 구조화하는 방법이다.

중요한 부분은 "것처럼 보이도록" 한다는 것이다.

 

자바스크립트는 싱글스레드 언어이기 때문에 한 번에 하나의 작업만 처리할 수 있다.

하지만 막상 인터넷 브라우저를 열고 서핑을 시작하면 모든 작업들이 동시에 처리되는 것처럼 보인다.

 

이것이 가능한 이유는 하나의 코어가 2개 이상의 작업을 재빠르게 변경하며 조금씩 진행하고 있기 때문이다.

독립적인 작업을 재빠르게 변경하기 위해서는 최소한의 작업 단위가 나누어져 있어야할테고,

어떤 순서로 진행되어야할지 예정이 되어있어야한다.

이를 가능케하는 것은 스케줄러이다.

 


 

웹 브라우저를 만들 수 있는 유일한 언어인 자바스크립트는 불행하게도 싱글스레드 언어이다.

브라우저를 렌더링하기 위해서는 수많은 작업이 발생하는데 그동안은 다른 작업을 할 수 없다.

 

화면이 오래 멈춰있다면 사용자 경험은 악화되기 때문에 웹 개발에 있어서 큰 걸림돌이 된다.

React는 렌더링 블록킹 문제를 해결하기 위해 동시성을 이용하게 되었다.

다음은 18버전에서 추가된 스펙들이다.


startTransition

작업 단위를 잘게 나누어 실행하되, 우선순위가 높은 작업(I/O 등)을 먼저 렌더링하고

우선순위가 낮은 작업은 잘게 나누어진 단위에서 블록시킨다.

 

예를 들면 검색창에 검색을 할 경우 키보드 입력에 따라 추천 검색어가 뜬다. 

키보드 입력이 한 번 발생할 때마다 입력된 키워드에 대해 관련된 단어들에 대한 데이터를 요청하여 매번 새롭게 렌더링을 해야하는데,

네이버 같이 방대한 데이터베이스를 가진 검색엔진의 경우 엄청난 부하가 걸릴 수 밖에 없다.

 

다행히도 사용자는 키보드 입력은 즉시 일어날 것으로 기대하지만, 추천 검색어에 대한 렌더링이 즉시 일어날 것이라고는 기대하지 않는다.

startTransition은 이러한 사용자의 심리를 이용한 스펙이다.

 

우선순위에 따른 작업을 분리하여 높은 우선순위의 작업(키보드 입력)은 즉각 렌더링에 반영하고,

낮은 우선순위의 작업(추천 검색어)은 렌더링을 지연시켜 최종 상태(입력이 완료된 단어에 대한 추천검색어)만 반영한다.

 

대략적인 구현은 다음과 같다.

// 높은 우선순위 업데이트 예약
setStateA(val1)

// 상태 업데이트를 전환으로 표시
startTransition(() => {
	// 낮은 우선순위 업데이트 예약
	setStateB(val2)
}
let isInTransition = false

function startTransition(fn) {
	isInTransition = true
	fn()
	isInTransition = false
}

function setState(value) {
	stateQueue.push({
    	nextState: value,
        isTransition: isInTransition
    }
}

어떻게 작동하는가?

  1. yielding: 렌더링과정을 작게 분할하고 일시중지 할 수 있음(중요한 일을 양보)
  2. interrupting: 동시성 모드에서 업데이트에 대해 우선순위가 있음
  3. 이전 결과 건너뛰기: 현재 상태만 방영하도록 중간 상태 반영을 건너 뜀. 필요한 최종 상태만 반영

setTimeout과 어떻게 다른가?

  • setTimeout으로 큐에 들어간 작업은 들어간 순서대로 처리되고, 취소될 수 없다.
  • 이전 결과 건너뛰기가 불가능하다.

우선순위에 따른 작업 분류

높은 우선순위 (urgent update)

  • 입력, 클릭, 누르기 등과 같은 직접적인 상호작용 반영
  • 즉작적 응답이 필요한 작업
  • 리액트18은 업데이트를 기본적으로 urgent로 처리한다.

낮은 우선순위 (transition update)

  • 하나의 뷰에서 다른 뷰로 전환하기
  • 전환되는 중간과정을 기대하기 않는다
  • 사용자는 결과에 시간이 소요되는 것을 예상하고 있다
  • load(로딩) transition, refresh(새로고침) transition

Streaming SSR with selective hydration

startTransition이 CPU 바운드의 개선이라면 이건 I/O 바운드의 개선이다.

기존 SSR의 문제

  • 어떤 것이라도 보여주기 위해 모든 data를 fetch해야 함.
  • 어떤 것이라도 hydrate하기 전에 필요한 모든 것을 로드해야 함.
  • 어떤 것이라도 상호작용하기 위해 모든 부분을 hydrate해야 함.

case1. 필요한 데이터를 모두 불러오기 전에 HTML 스트리밍하기

  • 특정 컴포넌트를 suspense로 감싸면 필요한 데이터를 기다리지 않는다. 대신 loading UI를 보여줄 수 있음.
  • data fetch가 끝나면 약간의 script 조각과 함께 HTML Stream으로 전송한다.

case2. 선택적 hydration

  • suspense를 제외한 부분이 먼저 렌더링되고 hydration된다.
  • suspense를 감싸면 스트림에서 다른 부분을 블록하지 않는다.

case3. 모든 html이 스트리밍 되기 전에 하이드레이션

  • html이 스트리밍되지 않고, js가 먼저 도착했다면 나머지 스트리밍 된 부분을 먼저 하이드레이션 한다.

case4. 모든 컴포넌트가 하이드레이션되기 전에 상호작용

  • 리액트 18에서 하이드레이션은 매우 작은 간격으로 실행되므로 메인 스레드를 블록하지 않음.

Automatic Batching

React 17 이전에 상태 업데이트는 이벤트 핸들러 안에서만 배치로 작동하였다.

React 18 이후 createRoot() API를 사용하면 모든 배치는 자동 배치로 작동한다.

 

이 곳에 자세한 내용이 있으니 참고하자.