Thumbnail

11분

React 18이 애플리케이션 성능을 향상시키는 방법

Vercel 블로그에 올라온 글인데 내용이 좋아서 읽어보면서 정리할겸 옮겨보았습니다.

들어가면서

Transition, Suspense, React Server Component 등 React 18과 함께 공개된 Concurrent 기능이 애플리케이션 성능을 어떻게 향상시키는 걸까요?

React 18은 React 애플리케이션의 렌더링 방식을 근본적으로 바꾸는 Concurrent mode를 도입했습니다. 이 기능들이 애플리케이션 성능에 어떤 영향을 미치고 개선하는지 알아보겠습니다.

메인 스레드와 Long Task

모두들 아시다싶이 브라우저에서 JavaScript를 실행하면 JavaScript 엔진은 단일 스레드에서 코드를 실행하는데, 이를 메인 스레드 라고 합니다. 메인 스레드는 JavaScript 코드를 실행하는 것을 포함하여 클릭, 사용자 상호작용 관리, 네트워크 이벤트 처리, 타이머, 애니메이션 업데이트, 브라우저 Reflow와 Repaint 등과 같은 다른 작업도 함께 처리합니다.

Flow of main thread at a browser
메인스레드는 작업을 하나씩 처리합니다

스레드가 하나라는 것은 한 번에 하나의 작업만 처리할 수 있다는 것을 의미합니다. 작업이 처리되는 동안 다른 모든 작업은 대기해야 합니다. small task는 브라우저에서 부드럽게 실행되어 원활한 사용자 경험을 제공하지만, long task는 다른 작업이 처리되는 것을 차단(Block)할 수 있으므로 문제가 될 수 있습니다.

실행하는데 50ms 이상 걸리는 모든 작업이 long task로 간주됩니다.

A diagram of a long task

왜 50ms 벤치마크 일까요? 원활한 시각적 경험을 제공하기 위해 디바이스가 16ms(60fps)마다 새 프레임을 생성해야 한다는 사실을 기반으로 합니다. 그러나 디바이스(브라우저)는 사용자 입력에 응답하고 JavaScript 실행 등의 다른 작업도 함께 수행해야 합니다.

애니메이션과 움직임이 부드럽게 보이기 위해서는 최소 1초당 최소 60프레임을 보여주는 것이 필요합니다. 이러한 부드러운 애니메이션을 제공하려면 각 프레임은 약 16.67ms(1000ms / 60) 이내에 렌더링되어야 합니다.

50ms는 기기가 프레임을 렌더링하고 다른 작업을 수행하는데 리소스를 할당할 수 있도록 하며, 디바이스가 원활한 시각적 경험을 유지하면서 다른 작업을 수행할 수 있도록 최대 33.33ms를 추가로 제공합니다. 더 자세한 내용은 RAIL 모델을 다루는 이 블로그에서 확인할 수 있습니다.

최적의 성능을 위해서는 long task 수를 최소화하는 것이 중요합니다. 웹사이트 성능 측정에 대해 long task가 애플리케이션 성능에 영향을 미치는 두 가지 지표가 있습니다: Total Blocking Time와 Interaction to Next Paint입니다.

Total Blocking Time(TBT)First Contentful Paint(FCP)Time To Interactive(TTI) 사이의 시간을 측정하는 중요한 지표입니다. TBT는 task가 실행되는데 50ms 이상 걸린 시간의 합계와 같습니다. 이는 작업을 실행하는데 있어서 사용자 경험에 큰 영향을 미칠 수 있습니다.

A diagram of Total Blocking Time

TTI 이전에 50ms보다 오래 걸린 작업이 두 개 있는데, 각각 50ms 값을 30ms와 15ms가 초과되었습니다. 따라서 TBT는 45ms입니다. Total Blocking Time은 이러한 값을 합산한 값입니다: 30ms + 15ms = 45ms

새로운 Core Web Vitals 지표인 INP(Interaction to Next Paint)는 사용자가 페이지와 처음 상호작용(예: 버튼 클릭)을 한 후 이 상호작용이 화면에 표시되는 시점, 즉 다음 페인트까지 걸린 시간을 측정합니다. 이 지표는 이커머스 사이트나 소셜 미디어 플랫폼과 같은 사용자 상호작요이 많은 페이지에서 특히 중요합니다. 사용자의 현재 방문 시간 동안의 모든 INP 값을 합산하여 가장 최악의 점수를 반환하는 방식으로 측정됩니다.

Core Web Vitals은 Google이 웹 페이지의 사용자 경험을 개선하고 웹 개발자들이 성능 최적화를 수행할 수 있도록 하기 위해 제안한 지표들로, 검색 엔진 최적화(SEO) 측면에서도 중요한 역할을 합니다. LCP, FID, CLS로 구성되어 있습니다. 최근에는 INP(Interaction to Next Paint)를 새로운 Core Web Vitals로 발표하면서 기존 FID(First Input Delay) 지표를 2024년 3월에 대체할 것이라고 발표했습니다.
Core Web Vitals에 대한 자세한 내용은 이 블로그와 제가 옮긴 이 글 도 참고하시면 좋습니다.

A diagram of Interaction to Next Paint
Interaction to Next Paint(INP)는 측정된 지표 중 가장 지연이 높은 250ms 입니다.

새로운 React 업데이트가 이러한 지표를 어떻게 최적화하여 사용자 경험을 개선하는지 이해하려면 먼저 기존 React가 어떻게 작동하는지 이해하는 것이 중요합니다.

기존 React의 작동 방식

React에서 시각적 업데이트는 렌더링 단계와 커밋 단계로 나뉩니다. React에서 렌더링 단계는 React 엘리먼트가 기존 DOM과 조정되는(즉, 비교되는) 순수 계산 단계입니다. 이 단계에서는 "Virtual DOM"이라고도 하는 새로운 React 엘리먼트 트리를 생성하는데, 이는 본질적으로 실제 DOM의 경량 인메모리 복사본입니다.

렌더링 단계에서 React는 현재 DOM과 새로운 React 컴포넌트 트리 사이의 차이점을 계산하고 필요한 업데이트를 준비합니다.

A diagram of a rendering phase in react

렌더링 단계 다음에는 커밋 단계입니다. 이 단계에서 React는 렌더링 단계에서 계산된 업데이트를 실제 DOM에 적용합니다. 여기에는 새로운 React 컴포넌트 트리를 표현하기 위해 DOM 노드를 생성, 업데이트, 삭제하는 작업이 포함됩니다.

기존 동기식 렌더링에서 React는 컴포넌트 트리 내의 모든 엘리먼트에 동일한 우선순위를 부여합니다. 컴포넌트 트리가 렌더링될 때, 초기 렌더링이나 상태 변경 시, React는 중단 없이 단일 작업으로 트리를 렌더링한 후 DOM에 커밋하여 화면에 컴포넌트를 시각적으로 업데이트합니다.

A diagram of a synchronous rendering

동기식 렌더링은 렌더링을 시작한 컴포넌트가 항상 렌더링이 완료되도록 보장하는 "all-or-nothing" 작업입니다. 컴포넌트의 복잡성에 따라 렌더링 단계가 완료되는 데 시간이 걸릴 수 있습니다. 이 시간 동안 메인 스레드가 차단되므로 애플리케이션과 상호작용하려는 사용자는 React가 렌더링을 완료하고 결과를 DOM에 커밋할 떄까지 응답하지 않는 UI를 경험하게 됩니다.

다음 데모에서 이를 시연할 수 있습니다. 텍스트 input 값에 따라 필터링되는 도시 리스트가 있습니다. 동기식 렌더링에서 React는 각 키 입력에 대해 CitiesList 컴포너트를 다시 렌더링합니다. 목록이 수만 개의 도시로 구성되어 있기 때문에 계산 비용이 상당히 많이 들기 때문에 키 입력과 input 입력에 반영되는 것 사이에 분명한 시각적 피드백 지연이 있습니다.

맥북과 같은 고사양 디바이스를 사용하는 경우 CPU 4x 쓰로틀하여 저사양 디바이스를 시뮬레이션 할 수 있습니다. 이 설정은 devtools > performance > ⚙️ > cpu 에서 설정할 수 있습니다.

performance 탭을 보면 모든 키 입력에서 최적 상태가 아닌 long task가 발생한 것을 볼 수 있습니다.

A screenshot of a browser performance tab for long tasks

빨간색 모서리로 표시된 작업은 "long task"로 간주됩니다. Total Blocking Time은 4425.40ms 입니다.

이러한 시나리오에서 React 개발자는 보통 debounce와 같은 서드파티 라이브러리를 사용해 렌더링을 지연시키곤 했지만, React 내장 솔루션이 없었습니다.

React 18은 백그라운드에서 동작하는 새로운 동시성 렌더러를 소개합니다. 이 렌더러는 특정 렌더링을 긴급하지 않은 것(non-urgent)로 표시할 수 있는 몇 가지 방법을 안내합니다.

A diagram of a concurrent rendering priorities

우선순위가 낮은 컴포넌트(분홍색)를 렌더링할 때, React는 더 중요한 작업을 확인하기 위해 메인 스레드로 돌아갑니다.

이 경우 React는 5ms 마다 메인 스레드에 양보하여 사용자 입력이나 그 순간 사용자 경험에 더 중요한 작업이 있는지 확인합니다. 메인 스레드에 지속적으로 양보함으로써 React는 이러한 렌더링을 차단하지 않고 더 중요한 작업의 우선순위를 렌더링 할 수 있습니다.

A diagram of a yield to main thread for prioriting component

모든 렌더링에 대해 중단 불가능한 단일 작업 대신, 동시성 렌더러는 우선순위가 낮은 컴포넌트를 (재)렌더링하는 동안 5ms 간격으로 메인 스레드에 제어권을 양보합니다.

또한 동시성 렌더러는 결과를 즉시 커밋하지 않고 백그라운드에서 해당 컴포넌트 트리의 여러 버전을 "동시에" 렌더할 수 있습니다.

동기식 렌더러는 all-or-nothing 계산인 반면, 동시성 렌더러를 사용하면 React가 하나 또는 여러 컴포넌트 트리의 렌더링을 멈추었다가 다시 시작하여 최적의 사용자 경험을 얻을 수 있습니다.

A diagram of a concurrent rendering flow diagram
React는 사용자 경험(우선순위가 더 높다고 보이는)에 따라 현재 렌더링을 잠시 멈춥니다.

동시성 기능을 사용하면 React는 사용자 상호작용과 같은 외부 이벤트에 따라 컴포넌트의 렌더링을 잠깐 멈췄다가 다시 시작할 수 있습니다. 사용자가 ComponentTwo와 상호작용을 시작하면 React는 현재 렌더링을 잠깐 멈추고 우선순위를 지정해 ComponentTwo를 렌더링한 후 ComponentOne 렌더링을 다시 시작했습니다. 이에 대해서는 Suspense 영역에서 자세히 설명합니다.

Transitions

useTransition hook으로 제공되는 startTransition 함수를 사용하여 업데이트를 긴급하지 않은(non-urgent)로 표시할 수 있습니다. 이는 특정 상태 업데이트를 "transition"으로 표시할 수 있는 강력한 새로운 기능입니다.

상태 업데이트를 startTransition로 감싸면 현재 사용자 상호작용을 유지하기 위해 렌더링을 연기하거나 중단해도 괜찮다고 React에게 알릴 수 있습니다.

import { useTransition } from "react"
 
function Button() {
  const [isPending, startTransition] = useTransition()
 
  return (
    <button
      onClick={() => {
        urgentUpdate()
        startTransition(() => {
          nonUrgentUpdate()
        })
      }}
    >
      ...
    </button>
  )
}

transition이 시작되면 동시성 렌더러가 백그라운드에서 새로운 트리를 준비합니다. 렌더링이 완료되면 React 스케쥴러가 새로운 상태를 반영해 DOM을 좋은 성능으로 업데이트할 수 있을 때까지 결과를 메모리에 유지합니다. 브라우저가 유휴 상태이고 우선순위가 더 높은 작업(사용자 상호작용과 같은)이 보류 중이 아닐 때를 말합니다.

A diagram of using a startTransition hook

CitiesList 데모에 transition을 사용하는 것은 적합한 예입니다. 각 키 입력에 대해 동기식 searchQuery 파라미터로 값을 직접 업데이트하는 대신, 상태를 두 개의 값으로 분할하고 searchQuery상태 업데이트를 startTransition으로 감쌀 수 있습니다.

이는 상태 업데이트가 사용자에게 혼란을 줄 수 있는 시각적 변화를 초래할 수 있다고 React에게 알리며, 따라서 React는 업데이트를 즉시 커밋하지 않고 백그라운드에서 새로운 상태를 준비하면서 현재 UI를 상호작용 가능한 상태로 유지할 수 있습니다.

이제 input 필드에 텍스트를 입력하면 키 입력 사이에 시각적인 지연 없이 사용자 입력이 부드럽게 유지됩니다. 이는 text 상태가 여전히 동기적으로 업데이트되고 input 필드가 해당 상태의 value를 사용하기 때문에 발생합니다.

백그라운드에서 React는 각 키 입력에 대해 새로운 트리를 렌더링하기 시작합니다. 하지만 React는 "all-or-nothing"인 동기식 작업을 하는 대신, 현재의 UI("이전" 상태를 표시)가 추가 사용자 입력에 응답하는 동안 메모리에 새로운 버전의 컴포넌트 트리를 준비하기 시작합니다.

performance 탭을 보면 상태 업데이트를 startTransition으로 감싼 transition이 감싸지 않은 transition에 비해 long task의 수와 Total Blocking Time이 훨씬 감소한 것을 확인할 수 있습니다.

A screenshot of the performance tab with the startTransition hook and without
performance 탭은 long task의 수와 Total Blocking Time이 크게 감소했음을 보여줍니다.

transition은 React 렌더링 모델의 근본적인 변화의 일부로, React가 여러 버전의 UI를 동시에 렌더링하고 서로 다른 작업 간의 우선순위를 관리할 수 있게 해줍니다. 이를 통해 빈번하게 업데이트되거나 CPU 집약적인 렌더링 작업을 처리할 때 더 부드럽고 반응성 있는 사용자 경험을 제공할 수 있습니다.

React Server Components

React Server Components는 React 18에서 experimental 기능이지만 프레임워크에서 사용될 준비가 되어 있습니다.

기존 React는 앱을 렌더링하는데 몇 가지 주요 방법을 제공했습니다. 모든 컴포넌트를 클라이언트에서 렌더링하거나(Client-Side Rendering), 또는 서버에서 컴포넌트 트리를 HTML로 렌더링하고 이 정적 HTML을 JavaScript 번들과 함께 클라이언트로 전송하여 클라이언트에서 컴포넌트를 hydrate(Server-Side Rendering)하는 것이었습니다.

A diagram of Client-Side Rendering and Server-Side Rendering

두 접근 방식 모두 동기식 React 렌더러가 컴포넌트 트리를 이미 서버에서 사용가능하더라도 클라이언트에서 제공된 JavaScript 번들을 통해 컴포넌트 트리를 다시 빌드해야 한다는 사실에 의존합니다.

React Server Components를 사용하면 React가 실제 직렬화된 컴포넌트 트리를 클라이언트로 전송할 수 있습니다. 클라이언트 측 React 렌더러는 이 형식을 이해하고 HTML 파일이나 JavaScript 번들로 전송하지 않고도 React 컴포넌트 트리를 성능적으로 재구성합니다.

A diagram of React Server Components Rendering

이 새로운 렌더링 패턴은 react-server-dom-webpack/serverrenderToPipeableStream 메소드와 react-dom/clientcreateRoot 메소드를 결합하여 사용할 수 있습니다.

// server/index.js
import App from '../src/App.js'
app.get('/rsc', async function(req, res) {
  const {pipe} = renderToPipeableStream(React.createElement(App));
  return pipe(res);
});
 
 
// src/index.js
import { createRoot } from 'react-dom/client';
import { createFromFetch } from 'react-server-dom-webpack/client';
export function Index() {
  ...
  return createFromFetch(fetch('/rsc'));
}
const root = createRoot(document.getElementById('root'));
root.render(<Index />);

⚠️ 데모를 매우 단순화한 (!) 예시입니다.

여기를 클릭하면 데모 전체를 확인할 수 있습니다.

기본적으로 React는 React Server Component를 hydrate 하지 않습니다. 해당 컴포넌트는 window객체에 접근하는 것과 같은 클라이언트 상호작용을 사용하거나 useState또는 useEffect와 같은 hook을 사용하지 않을 것으로 예상합니다.

컴포넌트를 상호작용하게(window 객체에 접근하는 것이나 useState, useEffect를 사용하는 것과 같은) 만들려면 파일 상단에 "use client" 번들러 지시문을 사용하면 됩니다. 이는 번들러에게 이 컴포넌트를 클라이언트 번들에 추가하도록 지시하고 React가 클라이언트 사이드 트리에 hydrate하여 상호작용을 추가하도록 지시합니다. 이러한 컴포넌트를 Client Components 라고 합니다.

A diagram of a shipping React Server Components and Client Components to client

참고: 프레임워크 구현은 다를 수 있습니다. 예를 들어 Next.js는 기존 SSR 접근 방식과 유사하게 Client Components를 서버에서 HTML로 prerendering 합니다. 그러나 기본적으로 Client Components는 CSR 접근 방식과 비슷하게 렌더링됩니다.

Client Components로 개발할 때 번들 크기를 최적화하는 것은 개발자에게 달려있습니다. 개발자는 다음과 같이 할 수 있습니다:

  • 상호작용 컴포넌트의 최상위 노드에서만 "use client" 지시문을 정의하도록 합니다. 이를 위해 컴포넌트 디커플링이 필요할 수 있습니다.
  • 컴포넌트 트리를 바로 import 하지 않고 props로 전달하기. 이렇게 하면 React가 클라이언트 번들에 추가하지 않고도 children을 React Server Components로 렌더링할 수 있습니다.
// src/client-component.js
'use client';
 
import { useState } from 'react';
 
export const ClientComponent = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
};
 
---
// src/server-component.js
import { ClientComponent } from './client-component.js';
 
export const ServerComponent = () => {
  return <ClientComponent />;
};
 
---
// src/parent.js
 
// client component를 바로 import 하는 대신에 props로 전달합니다.
// 이렇게 하면 React가 클라이언트 번들에 추가하지 않고도 `children`을
// React Server Components로 렌더링할 수 있습니다.
import { ServerComponent } from './server-component.js';
 
export const Parent = () => {
  return <ServerComponent />;
};

Suspense

또 다른 중요한 새로운 동시성 기능은 Suspense입니다. SuspenseReact.lazy를 사용한 코드 분할을 위해 React 16에서 출시되었기 때문에 완전히 새로운 기능은 아니지만, React 18에 도입된 새로운 추가기능은 Suspense를 데이터 fetch까지 확장합니다.

Suspense를 사용하면 원격 소스에서 데이터를 로드하는 것과 같은 특정 조건이 충족될 때까지 컴포넌트 렌더링을 지연시킬 수 있습니다. 그 동안 해당 컴포넌트가 아직 로드 중임을 나타내는 fallback 컴포넌트를 렌더링할 수 있습니다.

로딩 상태를 선언적으로 정의함으로써 조건부 렌더링 로직을 줄일 수 있습니다. Suspense를 React Server Components와 함께 사용하면 데이터베이스나 파일시스템과 같은 별도의 API 엔드포인트 없이도 서버 측 데이터 소스에 직접 접근할 수 있습니다.

async function BlogPosts() {
  const posts = await db.posts.findAll()
  return "..."
}
 
export default function Page() {
  return (
    <Suspense fallback={<Skeleton />}>
      <BlogPosts />
    </Suspense>
  )
}

React Server Components를 Suspense와 함께 원활하게 작동하므로 컴포넌트가 로딩되는 동안 로딩 상태를 정의할 수 있습니다.

Suspense의 진정한 힘은 React의 동시성 기능과 긴밀하게 통합될 때 나타납니다. 예를 들어 컴포넌트가 데이터 로드를 기다리는 중이어서 일시 중단된 경우, React는 컴포넌트가 데이터를 수신할 떄까지 유휴 상태로 있지 않습니다. 대신, React는 일시중단된 컴포넌트의 렌더링을 중단하고 다른 작업을 수행합니다.

A diagram of processing a react suspense

이 시간 동안 해당 컴포넌트가 여전히 로딩 중임을 나타내는 fallback UI를 렌더링하도록 React에 지시할 수 있습니다. await된 데이터를 사용할 수 시점이 되면, React는 이전 transition에서 보았던 것처럼 이전에 일시중단된 컴포넌트의 렌더링을 원활하게 재개할 수 있습니다.

또한 React는 사용자 상호작용에 따라 컴포넌트의 우선순위를 재조정할 수 있습니다. 예를 들어 사용자가 현재 렌더링되지 않은 일시중단된 컴포넌트와 상호작용하면 React는 진행 중인 렌더링을 일시중단하고 사용자가 상호작용하는 컴포넌트의 우선순위를 높일 수 있습니다. 가령 사용자가 렌더링 중인 버튼을 클릭하면, React는 렌더링하는 동안 다른 컴포넌트의 렌더링의 우선순위보다 높게 설정할 수 있습니다.

A diagram of reprioritize rendering when user interact in rendering

컴포넌트가 준비되면 React는 DOM에 커밋하고 이전 렌더링을 재개합니다. 이렇게 하면 사용자 상호작용에 높은 우선순위가 부여되고 사용자 입력에 따라 UI가 반응하게 되고 또한 시용자 입력에 따른 최신 상태로 유지됩니다.

Suspense와 React Server Component의 스트리밍 가능한 형식을 결합하면 우선순위가 높은 업데이트는 우선순위가 낮은 렌더링 작업이 완료될 때까지 기다릴 필요 없이 준비되는 즉시 클라이언테 전송할 수 있습니다. 따라서 클라이언트가 데이터 처리를 더 빨리 시작할 수 있으며, non-blocking 방식으로, 콘텐츠가 도착하면 점진적으로 나타나 보다 원활한 사용자 경험을 제공할 수 있습니다.

이러한 중단없는 렌더링 메커니즘은 비동기 작업을 다루는 Suspense 기능과 결합되어 특히 데이터 fetch가 많이 필요한 복잡한 애플리케이션에서 훨씬 더 원활하고 사용자 중심적인 경험을 제공할 수 있습니다.

Data Fetching

렌더링 업데이트 외에도 React 18에는 데이터를 fetch하고 결과를 효율적으로 기억할 수 있는 새로운 API가 도입되었습니다.

React 18에는 함수 호출 결과를 기억하는 캐시 함수가 있습니다. 동일한 렌더링 패스에서 동일한 인자를 사용하여 동일한 함수를 호출하면 함수를 다시 실행할 필요없이 메모된 값을 사용합니다.

import { cache } from "react"
 
export const getUser = cache(async (id) => {
  const user = await db.user.findUnique({ id })
  return user
})
 
getUser(1)
getUser(1) // 동일한 렌더링 패스에서 호출: 메모된 값을 반환

fetch 호출에서 React 18은 cache를 사용할 필요 없이 기본적으로 이와 유사한 캐싱 메커니즘을 포함합니다. 이를 통해 단일 렌더링 패스에서 네트워크 요청 횟수를 줄여 애플리케이션 성능을 개선하고 API 비용을 절감할 수 있습니다.

export const fetchPost = (id) => {
  const res = await fetch(`https://.../posts/${id}`);
  const data = await res.json();
  return { post: data.post }
}
 
fetchPost(1)
fetchPost(1) // 동일한 렌더링 패스에서 호출: 메모된 값을 반환.

이러한 기능은 Context API에 접근할 수 없는 React Server Components로 작업할 때 유용합니다. 캐시 및 fetch의 자동 캐싱 동작을 통해 전역 모듈에서 단일 함수를 export하여 애플리케이션 전체에서 재사용할 수 있습니다.

A diagram of cache mechanism for data-fetch

async function fetchBlogPost(id) {
  const res = await fetch(`/api/posts/${id}`)
  return res.json()
}
 
async function BlogPostLayout() {
  const post = await fetchBlogPost("123")
  return "..."
}
 
async function BlogPostContent() {
  const post = await fetchBlogPost("123") // BlogPostLayout이 먼저 호출되었을 때 메모된 값을 반환
  return "..."
}
 
export default function Page() {
  return (
    <BlogPostLayout>
      <BlogPostContent />
    </BlogPostLayout>
  )
}

요약

요약하면, React 18의 최신 기능은 여러가지 방식으로 성능을 향상시킵니다.

  • 동시성 React를 사용하면 렌더링 프로세스를 일시중지했다가 나중에 다시 시작하거나 중단할 수 있습니다. 즉, 대규모 렌더링 작업이 진행 중이더라도 UI가 사용자 입력에 즉각 반응할 수 있습니다.
  • Transition API를 사용하면 데이터 fetch 또는 화면 변경 중에 사용자 입력을 차단하지 않고도 부드럽게 전환할 수 있습니다.
  • React Server Components를 사용하면 개발자가 서버와 클라이언트 모두에서 동작하는 컴포넌트를 만들어 클라이언트 측 앱의 상호작용과 기존 서버 렌더링의 성능을 hydration 비용 없이 결합할 수 있습니다.
  • 확장된 Suspense 기능은 데이터 fetch에 시간이 오래 걸릴 수 있는 애플리케이션 부분보다 다른 컴포넌트가 먼저 렌더링되도록 하여 로딩 성능을 개선합니다.

마무리하며

React 18의 몇 가지 기능들을 몇 개의 블로그 글을 나눠가면서 작성하다보니 이제야 조금씩 이해가 되는 것 같습니다.

가장 쉽게 React 18의 동시성 모드를 사용할 수 있는 방법은 Next.js를 사용하는 것입니다. 최근 정식 릴리즈된 App Router를 포함하여 프레임워크를 사용하기만 하면 위에 나열된 기능을 거의 모두 쓸 수 있습니다. 즉, Next.js를 사용하면 React 18의 모든 장점을 쉽게 활용할 수 있습니다.

사용할 때 알면 좋은 몇 가지 프레임워크 종속적인 부분이 있긴 하지만, 문서도 잘 되어있고, 커뮤니티도 활발하니 Next.js를 사용하는 것을 추천드립니다.

Vercel 다음 글에서는 Next.js가 App Router에서 어떻게 이른 기능들을 활용하여 애플리케이션 성능을 개선하는지 작성한다고 하니 기대가 됩니다.

reference

마지막 업데이트

8/4/2023


Avatar

JHSeo

배우는 것을 좋아하고 관심이 많은 웹 엔지니어 입니다. 느리더라도 꾸준하게 성장하려고 노력하는 개발자입니다.