본문 바로가기

리액트 심화 스터디

[Week 7] Concurrent Mode, Suspense

1. Concurrent Mode

동시성이란?

동시성(Concurrency)은 작업을 동시에 처리할 수 있는 상태를 의미하며, 이를 위해 작업을 더 작은 독립적인 단위로 나누어 구조화하는 방식을 말한다.

리액트에서의 Concurrent Mode

자바스크립트는 싱글 스레드 언어로, 순차적으로 작업을 실행한다. 따라서, 작업이 실행되는 동안 스레드는 해당 작업이 끝날 때까지 블록되며 다음 작업은 실행되지 않는다. 

하지만 React 16 이전에는 렌더링이 동기적으로 이루어졌으며, 작업이 긴 경우 브라우저의 메인 스레드가 차단(blocking)되어 사용자 입력이나 애니메이션 같은 작업을 처리하지 못했다.

React 16에서는 렌더링 엔진을 새롭게 설계한 Fiber 아키텍처가 도입되면서, 렌더링 작업을 더 작은 단위로 나누고 우선순위를 기반으로 작업을 스케줄링할 수 있게 개선되었다. 

React 18에서는 이 동작이 더 정교해졌으며, Concurrent Features라는 동시성 기능이 기본 활성화되었다.

Concurrent Mode의 주요 기능

  1. 메인 스레드 블록 방지: UI가 항상 응답성을 유지하도록 메인 스레드 차단 X
  2. 작업 우선순위 관리: 중요한 작업을 우선 처리하고, 작업 간 전환 관리 (Time-slicing)
  3. 점진적 렌더링: 최종 결과로 확정하지 않고 부분적으로 트리 렌더링 가능

React 18에서의 Concurrent Mode

React 18에서는 createRoot API를 통해 동시성 기능이 기본 활성화된다.

// index.js

import ReactDOM from 'react-dom';
import App from './App'; 

const container = document.getElementById('app'); 

const root = ReactDOM.createRoot(container);

root.render(<App />);

 

Concurrent Mode에서 제공하는 기능

1️⃣ useTransition

useTransition은 React 18에서 도입된 훅으로, 상태 업데이트의 우선순위를 낮추고 비동기적으로 처리할 수 있게 한다.

이를 통해 고비용 작업이 UI의 반응성을 저해하지 않도록 한다.

  • startTransition: 상태 업데이트를 낮은 우선순위로 지정
  • isPending: 전환 진행 여부
 const [id, setId] = useState(1);
  const [startTransition, isPending] = useTransition();

  const onClick = (newId) => {
    startTransition(() => {
      setId(newId); // 상태 업데이트를 낮은 우선순위로 처리한다.
    });
  };

 

2️⃣ useDeferredValue

useDeferredValue는 값을 지연(defer) 처리하여 UI를 더 효율적으로 업데이트할 수 있도록 한다.

특정 값의 의존성에 따라 렌더링하는 컴포넌트가 우선순위가 낮은 작업으로 처리되도록 만들 수 있다.

import { useState, useDeferredValue } from 'react';
const [value, setValue] = useState('');
const deferredValue = useDeferredValue(value, { // 이 값에 의존하는 컴포넌트는 낮은 우선순위로 렌더링해도 괜찮아!
  timeoutMs: 5000
});

 


2. Suspense

저번 주차 스터디에서 가볍게 다뤘던 내용이다.

Suspense
React.lazy를 사용하여 컴포넌트를 동적으로 불러올 때, 컴포넌트를 로드하는 동안 잠깐의 지연이 발생할 수 있다.
이때 사용자는 잠깐 비어 있는 화면을 보게 되는데, Suspense는 이 화면을 채워준다.
Suspense는 로딩 중인 컴포넌트를 대신해 로딩 상태를 보여주고, 컴포넌트가 로드되면 해당 컴포넌트를 렌더링한다.

 

Suspense

Suspense는 React 18에서 정식으로 도입된 기능으로, 비동기 렌더링이 진행되는 동안 사용자에게 로딩 상태를 표시한다.

 

<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>
  • children: 실제 렌더링하려는 컴포넌트.
  • fallback: 실제 UI가 로드되기 전까지 대체되는 UI. 보통 로딩 스피너나 스켈레톤을 활용한다.

Suspense는 children의 렌더링이 지연되면 자동으로 fallback을 띄워주고, 데이터가 준비되면 children으로 전환시킨다.

만약, fallback의 렌더링이 지연되면, 가장 가까운 부모 Suspense가 활성화된다.

 

Suspense를 사용하는 이유

  • 사용자 경험 향상
    • 데이터를 가져오는 동안 스켈레톤 UI 로딩 컴포넌트를 표시함으로써 빈 화면 방지
  • 로직 단순화
    • 비동기 작업과 로딩 상태 관리를 컴포넌트 내부에서 처리할 필요 없이 선언형 방식으로 간결하게 구현 가능
  • 관심사 분리
    • 비동기 데이터 로드와 로딩 상태 관리 로직을 부모 컴포넌트로 위임

 

<SuspenseList>

SuspenseList는 여러 Suspense 컴포넌트를 감싸 렌더링 순서를 제어할 수 있는 기능이다.

  • revealOrder: Suspense 컴포넌트의 표시 순서 지정 ("forwards", "backwards").
  • tail: 리스트의 꼬리 부분 처리 방식 지정 ("collapsed", "hidden").
const [id, setId] = useState(1);
<SuspenseList revealOrder="forwards">
  <Suspense fallback={<Spinner />}>
    <Details id={id} />
  </Suspense>
  <Suspense fallback={<Spinner />}>
    <Comments id={id} />
  </Suspense>
</SuspenseList>

 

SuspenseList는 컴포넌트 표시 순서만 제어하며, 데이터 가져오기나 렌더링 순서와는 무관하다.

 

 

 

https://17.reactjs.org/docs/concurrent-mode-reference.html#suspenselist