본문 바로가기
나도 공부한다/React

리액트의 동시성 렌더링에 대해 알아보자 (리액트 인터뷰 가이드 5장 보충)

by 꾸빵이 2024. 11. 5.

서론

참고서에 동시성 렌더링 내용이 아주 조금 써있었는데 궁금해서 더 알아봤다.

 

등장 배경

기존 렌더링 방식의 문제

  • 렌더링을 시작해서 화면에 그리기까지 실행을 멈출 수 없었음
  • 리액트 변경 비교 알고리즘이 매우 빨라서 보통은 멈춰도 상관X. 하지만 연산이 길어진다면?
    → 프레임이 드롭되고 입력은 한참 후에 예약되면서 좋지 않은 사용자 경험 제공 ex)

여기서 프레임이 드롭된다는 것은 특정 순간에 리소스 로드, 연산 처리 등으로 화면이 평균 프레임 이하로 떨어졍서 끊기는 듯한 현상을 말한다. 디테일이 높은 게임을 해본 사람이면 한번쯤 경험해봤을 현상이다.


위 링크를 타고 들어가서 입력을 빠르게 해보자. 처음에는 괜찮은데 그려야될 네모가 많아질 수록 버벅거리며 심지어 화면이 일정시간동안 멈추기도 한다.

 

이를 해결하기 위한 시도, but…

디바운스와 쓰로틀링 → 개발자가 설정한 지연 시간에 의존

  • 주기를 짧게 잡으면 성능이 나쁜 컴퓨터에서는 버벅거림
  • 주기를 길게 잡으면 성능이 좋은 컴퓨터에서는 불필요한 지연
  • 사용자 입력 중에는 작업이 처리되지 않음

⇒ 동시성으로 문제를 해결해보자!

 

 

그래서 동시성이 뭔데?

동시성 VS 병렬성

  • 동시성
    • 여러 작업이 동시에 처리되는 것처럼 보이지만 실제로는 작업을 잘게 나누어 번갈아가며 실행하여 동시에 실행되는 것처럼 보이게 프로그램 구조화
  • 병렬성
    • 여러 작업이 실제로 동시에 처리됨

 

동시성 렌더링 동작 방식

  • 하나의 컴포넌트 렌더링을 잘게 분해하여 처리
  • 작업을 마치고 메인 스레드에 일정 시간을 양보 → 그 시간에 브라우저는 이벤트 처리
  • 우선순위가 더 높은 컴포넌트가 들어왔을 때
    • 기존 작업중이던 컴포넌트를 pending 상태로 만들고 새 컴포넌트의 렌더링과 페인팅 먼저 수행
    • 작업이 끝나면 pending 상태였던 낮은 순위 렌더링을 리베이스
    ⇒ 계산 속도를 개선하는 것이 아닌 UI 차단을 최소화 시키는 것

 

두가지 우선 순위

작업을 스위칭하는 기준은 우선순위이다.

  • 긴급 업데이트 (Urgent Update)
    • 입력, 클릭과 같은 즉각적인 응답이 필요한 상호작용
    • React 18은 업데이트 함수를 기본적으로 긴급으로 처리
  • 전환 업데이트 (Transition Update)
    • 사용자들이 많은 시간이 소요될 것이라고 예상하는 작업 ex) 페이지 로딩, 새로 고침 등
    • 하나의 view에서 다른 view로 전환하는 것
    • 전환되는 중간 과정이 필요 없을 때
    • Load Transition, Refresh Transition

여기서 input 창은 즉각적으로 반응해야하므로 긴급 업데이트, 자동완성 창은 전환 업데이트이다.

 

우선순위 낮추기 (for CPU 바운드 개선)

 

그렇다면 우선순위를 설정하는 방법은 무엇일까? startTransition 메서드를 사용하면 된다.

startTransition 메서드

  • set함수를 감싼다
    startTransition(()=>{
    	setState(value);
    })
  • 콜백 함수가 바로 실행되며 상태 업데이트들은 전환 업데이트로 처리되어 우선 순위가 낮아짐 = CPU 사용을 양보함
  • ex)
 

Concurrent

 

ajaxlab.github.io

 

 

'기존 렌더링 방식의 문제점' 부분에서 보여줬던 예시인데 거기에 startTransition을 추가한 것이라고 한다.

연속으로 입력했을 때 버벅거림이 없는건 아니지만 훨씬 매끄럽다.

 

startTransition이 동시성 렌더링을 구현하는 방식

  • Yielding : 렌더링 과정을 분할하고 중요한 작업을 양보 (ex) 5ms마다 한번씩 멈추기)
  • Interrupting : 우선 순위가 높은 작업이 생기면 중단
  • 이전 결과 건너뛰기 : 최종 상태만 업데이트 되도록 이전 상태 변경을 건너 뜀

useTransition

  • useTransition도 startTransition처럼 UI를 차단하지 않고 상태를 업데이트할 수 있음
  • startTransition 기능에 추가로 isPending 상태 제공 (문서)
  • isPending은 지연된 transition 여부를 알려주는 것으로, 지연 중이면 true 상태
{isPending ? (
        <div>로딩 중입니다...</div>
      ) : (
        <ul className="search-list">
          // 지연이 끝난 후 렌더링될 것
        </ul>
      )}

 

useDeferredValue

  • 마찬가지로 startTransition과 동일한 기능
  • set 함수에 접근할 수 없어 startTransition을 사용하지 못하거나 props를 지연하고 싶은 경우에 사용

 

startTransition VS setTimeout

  • setTimeout으로 큐에 들어간 작업은 취소될 수 없음, 비동기적
  • startTransition으로 변경된 내용은 이전 결과를 건너뛰고 최종 상태만 반영, 동기적
startTransaction(()=>{
	setSearchQuery("My");
})
startTransaction(()=>{
	setSearchQuery("MyNa");
})
startTransaction(()=>{
	setSearchQuery("MyName");
})

 

 

리액트 v18에서 I/O바운드를 개선할 수 있는 방법

동시성 렌더링은 CPU 바운드를 개선하는 방법이다. 그렇다면 I/O 바운드를 개선하는 방법에는 뭐가 있을까?

기존 SSR의 문제

  • 보여주기 위해 모든 data를 fecth 해야함
  • hydrate 하기 전에 필요한 모든 것을 로드해야함
  • 상호작용 하기 위해 모든 것을 hydrate 해야함
    → SSR에도 지연로딩이 필요하다!

 

Streaming SSR과 선택적 하이드레이션

streaming SSR (서버 측)

  • renderToString()을 대체할 수 있는 pipeToNodeWritable()가 생겼다.
    • renderToString은 트리를 HTML 문자열로 렌더링하는 것으로, HTML을 한번에 생성하여 반환한다.
      • 따라서 컴포넌트가 다른 처리를 하고 있어서 기다려야하면 빠르게 렌더링하지 못하는 문제가 발생함
      • 리액트는 트리를 한번만 hydrate하기 때문에 모든 컴포넌트에 대한 js 코드가 다운로드 되어야함 → 작은 컴포넌트도 큰 코드가 다운될 때까지 기다려야함
    • pipeToNodeWritable은 HTML을 부분적으로 전송 가능

 

선택적 하이드레이션 (클라이언트 측)

  • suspense
    • 어떤 문제를 해결하기 위해 도입되었나?
      • 이전에는 로딩 여부를 별도의 플래그 변수로 확인해야했음 (조건부 렌더링)
    • 언제 사용하는가?
      • 자식 컴포넌트가 렌더링될 준비가 될 때까지 로딩 인디케이터 같은 대체 UI를 표시하고 싶을 때
    • 사용 방법
      • Suspense 컴포넌트로 해당 컴포넌트를 감싸고 fallback prop에 대체 UI를 전달
      • 가장 가까운 상위의 서스펜스를 찾아 fallback을 로딩하기 때문에 이 코드처럼 Suspense 컴포넌트의 직접적인 자식이 아니어도 된다. 
        <Suspense fallback={<Loading />}>
          <Details artistId={artist.id} />
        </Suspense>
        
        function Details({ artistId }) {
          return (
            <>
              <Biography artistId={artistId} />
              <Panel>
                <Albums artistId={artistId} />
              </Panel>
            </>
          );
        }
    • suspense가 선택적 하이드레이션을 구현하는 방식
      • suspense를 제외한 부분이 먼저 렌더링되고 hydrate됨
      • data fetch가 끝나면 약간의 react 스크립트 조각이 HTML stream으로 전송됨
    • 모든 데이터에서 사용할 수 있는가? 그렇지 않다
      • 현재까지는 Next.js처럼 Suspense를 지원하는 프레임워크에서만 사용할 수 있다.
      • effect, 이벤트 핸들러 내부에서 데이터를 가져올 때는 감지하지 못한다.

 

출처

Inside React (동시성을 구현하는 기술) 

 

NAVER D2

Inside React(동시성을 구현하는 기술)

tv.naver.com

리액트 공식 문서

useTransition