React 18을 준비하세요.

최철헌
네이버 플레이스 개발 블로그
24 min readJul 8, 2021

요약

  • React 18의 알파 버전이 출시되었습니다. (정식 릴리즈는 아직입니다.)
  • 자동 배치(Automatic Batching)가 도입되어 배치가 개선되었습니다.
  • startTransition 이나 선택적(Selective) Hydartion 등의 동시성(Concurrent) 기능이 추가되었습니다.
  • 새로운 서버 사이드 렌더링 아키텍처가 도입되었습니다. 이 아키텍처에서는<Suspense>React.lazy 를 지원하며, 기존 서버 사이드 렌더링의 근본적인 문제를 해결했습니다.
  • ReactDOM.render() 가 Deprecated 되고 새롭게 ReactDOM.createRoot() API가 그 자리를 이어갑니다.

목록

  1. 개요
  2. React 18의 핵심 변경점
    1. 자동 배치(Automatic Batching)
    2. 동시성(Concurrent) 기능
    3. 서스펜스(Suspense)를 지원하는 새로운 서버 사이드 렌더링 아키텍처
  3. React 18로 업그레이드 하기
    1. 클라이언트
    2. 서버

1. 개요

지난 6월 중순, 리액트 팀은 React 17 정식 릴리즈 이후 약 8개월 만에 새로운 React 18 버전의 메이저 업데이트 소식을 전했습니다. 다만 아직은 알파 버전이기 때문에 프로덕션 애플리케이션에 적용하는 것은 무리가 있습니다. 지금까지는 정식 릴리즈 시 안내 포스팅을 했는데요. 이번 React 18은 이례적으로 정식 릴리즈 대신 알파 버전을 릴리즈하며 안내 포스팅을 올렸습니다.

그 이유는 이번 메이저 업데이트의 내용이 매우 방대하기 때문으로 보입니다. 특히 라이브러리를 만드는 서드 파티에서 React 18의 새로운 기능들을 지원해줄 수 있어야 사용자들이 빠르게 혜택을 볼 수 있겠죠. 그래서 서드 파티에서 빠르게 React 18을 지원하는 것을 돕기 위해 React Working Group을 만들어서 방대한 문서를 공유하고 있습니다.

이번 React 18의 정식 릴리즈는 앞으로 몇 달 뒤로 예정되어 있습니다. 하지만, 리액트 사용자 입장에서도 이번 React 18은 미리 알아두면 도움이 많이 됩니다. 지금까지의 웹 프론트엔드 개발에서 한 단계 진보하여 동시성 제어가 가능해지기 때문입니다.

이 글에서는 React 18의 중요한 새로운 기능 3가지에 관해 설명하고, 이전 리액트에서 React 18버전으로 업그레이드할 수 있는지 알려드리도록 하겠습니다.

2–1. 자동 배치(Automatic Batching)

여러분은 리액트의 배치(Batching)에 대해 알고 계시나요? 배치란, 리액트가 더 나은 성능을 위해 여러 개의 상태 업데이트를 한 번의 리렌더링(re-render)으로 묶는 작업을 뜻합니다.

아래 예시를 살펴 보겠습니다.

function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);

function handleClick() {
setCount(c => c + 1); // 아직 리렌더링 되지 않습니다.
setFlag(f => !f); // 아직 리렌더링 되지 않습니다.
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
}

return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}

위의 handleClick 이벤트 핸들러 함수에서는 상태 업데이트를 두 번 (setCount, setFlag) 수행했습니다. 하지만, 리액트는 언제나 배치를 수행하여 이 두 번의 상태 업데이트를 한 번의 리렌더링으로 처리합니다. 이를 통해 불필요한 여러 번의 리렌더링을 방지하고 의도치 않은 버그를 예방할 수 있습니다.

하지만 배치가 수행되지 않는 예외가 존재합니다.

function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);

function handleClick() {
fetchSomething().then(() => {
// 리액트 17 및 그 이전 버전에서는 배치가 수행되지 않습니다. 왜냐하면
// 이 코드들은 이벤트 이후의 콜백에서 실행되기 때문입니다.
setCount(c => c + 1); // 리렌더링
setFlag(f => !f); // 리렌더링
});
}

return (
<div>
<button onClick={handleClick}>Next</button>
<h1 style={{ color: flag ? "blue" : "black" }}>{count}</h1>
</div>
);
}

React 17 이전의 기존 리액트에서는 위와 같이 이벤트 핸들러 함수 내에서 실행되는 상태 업데이트가 아닌 경우 배치가 동작하지 않았습니다.

하지만 React 18부터 자동 배치(Automatic Batching)라는 것이 추가되었습니다. 자동 배치란, 위와 같이 일반적인 이벤트 핸들러 함수 스코프에서 상태 업데이트가 발생하지 않더라도 자동으로 배치를 적용해주는 것을 뜻합니다.

setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
}, 1000);

fetch(/*...*/).then(() => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
})

elm.addEventListener('click', () => {
setCount(c => c + 1);
setFlag(f => !f);
// 리액트는 오직 마지막에만 리렌더링을 한 번 수행합니다. (배치 적용)
});

이 자동 배치를 사용하기 위해서는 컴포넌트 트리를 기존의 ReactDOM.render 함수 대신 새로운 ReactDOM.createRoot 함수를 사용해야 하는데요. 이 부분은 뒤에서 자세히 다루도록 하겠습니다.

한편, 매우 드문 경우이기는 하나 상태 업데이트에 배치가 적용되지 않았으면 하는 경우가 있을 것입니다. 그 경우에는 새롭게 추가된ReactDOM.flushSync 함수를 사용하면 해결할 수 있습니다.

import { flushSync } from 'react-dom';

function handleClick() {
flushSync(() => {
setCounter(c => c + 1);
}); // 리액트는 즉시 DOM을 업데이트합니다.

flushSync(() => {
setFlag(f => !f);
}); // 리액트는 즉시 DOM을 업데이트합니다.
}

2–2. 동시성(Concurrent) 기능

지금까지 Concurrent Mode 라고 알려졌던 리액트의 차기 핵심 기능 중 일부가 React 18에 추가됩니다.

서버 컴포넌트와 빌트인 캐시 등을 포함하는 Data Fetching 솔루션은 React 18.0 릴리즈에 포함되지 않습니다.
이 부분은 아직 작업 중이고, React 18.x 에 적용 예정입니다.

이 동시성 기능을 이해하기 위해, 먼저 상태 업데이트를 어떻게 분류할 수 있는지에 대해 생각해 봐야 합니다.

상태 업데이트를 두 가지로 분류한다면 다음과 같이 할 수 있습니다.

  • 긴급 업데이트(Urgent updates): 직접적인 상호 작용 반영(타이핑, 오버, 스크롤링 등)
  • 전환 업데이트(Transition updates): 하나의 뷰에서 다른 뷰로의 UI 전환

긴급 업데이트는 사용자의 입력에 따라 즉각적으로 업데이트되지 않으면(화면 멈춤, 렉 등) 문제가 있다고 느끼는 영역입니다. 반면 전환 업데이트는 화면에 즉시 나타나는 걸 기대하지 않는 영역입니다.

네이버 검색 창을 예시로 보겠습니다.

사용자가 검색하기 위해 직접 “리액트"를 입력한다고 가정해 보겠습니다.

‘ㄹ', ‘ㅣ', ‘ㅇ', ‘ㅐ', ‘ㄱ', …각각의 키를 입력할 때, 키 입력이 올바르게 되었다는 결과는 즉각적으로 보여줘야 합니다. 사용자가 그렇게 되길 기대하기 때문입니다. 만약 이때 렉이나 화면 멈춤 등이 발생한다면 사용자는 부정적인 경험을 하게 됩니다.

반면 하단의 추천 검색어로 뜨는 자동 완성 영역의 경우 그렇지 않습니다. 만약 디바이스의 성능이 권장 사양보다 낮아 렌더링 성능이 낮거나, 네트워크 속도가 느린 상황 등의 이유로 업데이트가 늦어질 수도 있지요. 중요한 건 전환 업데이트 때문에 긴급 업데이트가 방해되면 안 됩니다.

하지만 React 17 까지는 상태 업데이트를 긴급 혹은 전환으로 명시하는 방법이 없었습니다. 모든 상태 업데이트는 긴급 업데이트였죠. 그래서 setTimeout이나 throttle, debounce 등의 테크닉을 써서 긴급 업데이트 방해를 우회하는 것이 최선이었습니다.

하지만 React 18부터는 startTransition API를 제공함으로써 전환 업데이트를 명시적으로 구분하여 상태 업데이트를 진행할 수 있게 되었습니다.

import { useTransition } from 'react';

function SearchBar() {
const [isPending, startTransition] = useTransition();

// ...

function handleChange(e) {
const input = e.target.value;

// 긴급 업데이트: 타이핑 결과를 보여준다.
setInputValue(input);

// 이 안의 모든 상태 업데이트는 전환 업데이트가 된다.
startTransition(() => {
// 전환 업데이트: 결과를 보여준다.
setSearchQuery(input);
});
}

// ...
}

참고로, 소수의 케이스에 대응하기 위해 Hook을 거치지 않고 React.startTransition을 사용할 수도 있습니다.

startTransition의 경우 크게 두 가지 Use Cases가 있습니다.

  • 느린 렌더링: 작업량이 많아 결과를 보여주기 위한 UI 전환까지 시간이 걸립니다.
  • 느린 네트워크: 네트워크로부터 데이터를 기다리기 위한 시간이 걸립니다. Suspense와 연계.

현재 각각의 경우 처리하는 방법이 다르다는 가이드는 있지만, 그 자세한 내용이나 다른 동시성(Concurrent) API에 대한 문서는 아직 공개되지 않았고 추후에 포스팅할 예정이라고 합니다.

문서 공개 후 기회가 된다면 자세하게 소개해 드리도록 하겠습니다.

2–3. 서스펜스(Suspense)를 지원하는 새로운 서버 사이드 렌더링 아키텍처

React 18 에서는 새로운 서버 사이드 렌더링(이하 SSR) 아키텍처가 적용되었습니다. 새롭게 pipeToNodeWritable API가 추가되었고, 이 API를 사용하면 SSR을 통해 <Suspense>를 사용할 수 있게 되었습니다.

즉, React.lazy를 서버 사이드 렌더링에서 사용할 수 있게 되었습니다.

기존의 리액트 SSR는 다음 4단계를 거쳤습니다.

  1. 서버에서 전체 앱의 데이터를 받습니다.(Data Fetching)
  2. 그 후, 서버에서 전체 앱을 HTML로 렌더링한 후 Response로 전송합니다.
  3. 그 후, 클라이언트에서 전체 앱의 자바스크립트 코드를 로드합니다.
  4. 그 후, 클라이언트에서 서버에서 생성된 전체 앱의 HTML과 자바스크립트 로직을 연결합니다(Hydration)

중요한 것은, 앱 전체를 대상으로 각 단계가 완료되어야만 다음 스텝으로 넘어갈 수 있다는 것입니다. 만약 전체 컴포넌트 트리 중 일부가 나머지 부분보다 느리다면, 서버 사이드 렌더링의 전체 성능은 급격하게 낮아지게 됩니다. 각 단계마다 그 느린 부분에서 병목 현상이 발생하기 때문이죠.

React 18에서는 <Suspense>를 사용하여 앱을 더 작은 독립적인 유닛으로 만들 수 있습니다. 이를 통해 앱의 나머지 부분의 렌더링을 방해하지 않게 할 수 있습니다.

새로운 서버 사이드 아키텍처를 통해 구체적으로 어떻게 우리 애플리케이션을 개선할 수 있을지 React 17 이전과 React 18을 비교해 보겠습니다.

React 17과 그 이전

<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Comments />
</RightPane>
</Layout>

위에서 언급한 리액트 SSR 4단계를 기준으로, 이 컴포넌트 트리의 렌더링이 어떻게 진행되는지 살펴보겠습니다.

1. 서버에서 전체 앱의 데이터를 받습니다(Data Fetching).

서버 사이드 렌더링을 수행하지 않는 일반적인 싱글 앱 애플리케이션(Single Page Application, SPA)에서는 자바스크립트가 로딩되는 동안 화면에 아무것도 나타나지 않습니다. 자바스크립트 로딩이 끝나야만 화면이 보이지요. 이 문제를 해결하기 위해 SPA 프레임워크(혹은 라이브러리)에서는 서버 사이드 렌더링을 지원해 주었지요. 만약 서버에서 애플리케이션을 미리 HTML로 렌더링하여 Response로 내려준다면, 이용자가 더 빠르게 애플리케이션 화면을 볼 수 있으니까요. 비록 자바스크립트 코드가 로딩되기 전이기 때문에 곧바로 상호 작용을 할 수는 없지만 말이죠.

2. 그 후, 서버에서 전체 앱을 HTML로 렌더링한 후 Response로 전송합니다.

회색 영역은 상호 작용이 불가능한 HTML 영역입니다.

클라이언트가 Response로 서버 사이드 렌더링의 결과물인 HTML을 받으면 사용자는 화면을 볼 수 있습니다. 자바스크립트 로딩이 안 된 HTML일 뿐이기 때문에 리액트 애플리케이션으로 구현된 상호 작용은 아직 할 수 없습니다. 링크나 폼 Input 입력과 같이 웹에 기본적으로 내장된 상호 작용만 할 수 있죠.

3. 그 후, 클라이언트에서 전체 앱의 자바스크립트 코드를 로드합니다.

4. 그 후, 클라이언트에서 서버에서 생성된 전체 앱의 HTML과 자바스크립트 로직을 연결합니다(Hydration)

녹색 영역은 Hydration 까지 끝나 상호 작용이 가능한 리액트 컴포넌트 영역입니다.

자바스크립트 코드 로딩이 끝나면, 리액트는 메모리 단에서 컴포넌트 트리를 렌더링합니다. 그 후 모든 자바스크립트 로직을 서버에서 Response로 내려받은 HTML에 연결하죠. 이 작업을 Hydration(수화)이라고 합니다. 메마른 HTML에 촉촉한 상호 작용 능력을 불어넣어 준다고 보면 됩니다.

이 Hydration이 끝나면 리액트 애플리케이션의 초기화가 완료됩니다.

서버 사이드 렌더링은 근본적인 애플리케이션 로딩을 빠르게 하지는 않습니다.

단지 리액트 애플리케이션의 초기화가 끝날 때까지 빈 화면을 보여주는 것 대신, 서버에서 렌더링한 HTML을 사용자에게 먼저 보여줘서 이용자로 하여금 앱이 느리지 않다고 인지할 수 있게 돕는 역할일 뿐입니다. 현재까지의 서버 사이드 렌더링의 의의는, 구글의 유명한 조사 결과인 “페이지 로딩이 3초를 넘어서면 모바일 이용자의 53%가 사이트 사용을 포기한다.” 라는 문제를 어느 정도 해결할 수 있다는 것, 그리고 SEO를 가능하게 해주는 것 정도입니다.

하지만 만약 특정 컴포넌트의 렌더링에 병목이 생긴다면 그마저도 쉽지 않습니다.

여기엔 3가지 문제점이 있을 수 있습니다.

  1. 특정 컴포넌트를 렌더링하는데 필요한 Data Fetching이 오래 걸린다면?
    서버는 HTML을 Response로 내려주는데 지연이 발생하므로, 이용자는 빈 화면을 오래 볼 수밖에 없습니다. 서버는 Data Fetching이 모두 끝나야 컴포넌트 트리를 렌더링을 하니까요.
  2. 특정 컴포넌트의 코드량이 커서 로딩이 오래 걸린다면?
    모든 자바스크립트 코드를 로딩하기 전에는 Hydaration 단계로 넘어갈 수 없습니다.
  3. 특정 컴포넌트의 로직이 복잡해서 렌더링하는 시간이 오래 걸린다면?
    먼저 위와 마찬가지로 서버에서 HTML을 내려줄 때도 시간이 걸릴 것이고, 이용자는 빈 화면을 오래 보게 될 것입니다. 서버는 리액트 앱 전체를 렌더링 완료해야 Response로 HTML을 내려주니까요.
    또한 Hydration 과정에서도 병목이 생길 겁니다. 그러므로 이 컴포넌트의 Hydration이 끝날 때까지 헤더나 내비게이션 등 다른 컴포넌트 역시 상호 작용을 할 수 없습니다. 앱이 상호 작용 할 수 있는 상태가 되려면 먼저 앱 전체가 Hydration이 완료되어야 하니까요.

이런 문제가 발생하는 근본적인 원인은 서버 사이드 렌더링의 한 과정에서 다음 과정으로 넘어가기 위해 “앱 전체가 각각의 단계를 완료해야” 하기 때문입니다.

이 문제를 해결하기 위해 React 18에서는 Suspense를 지원하는 새로운 서버 사이드 렌더링 아키텍처가 도입되었습니다.

React 18

React 18에는 <Suspense>와 연계하여 사용할 수 있는 두 가지 메이저 SSR 기능이 추가됩니다.

  • HTML 스트리밍: 서버 단에서, renderToString 대신 새로운 pipeToNodeWritable API를 사용하여 HTML을 스트리밍할 수 있습니다.

기존에도 스트리밍을 통해 서버 사이드 렌더링을 하는 renderToNodeStream API가 있었으나, Data Fetching을 기다릴 수 없는 반쪽짜리였습니다. 이제 이 API는 Deprecated 됩니다.

  • 선택적(Selective) Hydaration: 앱에서 렌더링 비용이 많이 드는 서브 컴포넌트 트리를 <Suspense>로 감싸서, 전체 앱의 Hydration을 방해하지 않고 별도의 Hydration을 진행할 수 있습니다.

pipeToNodeWritable API와 <Suspense> , 두 가지를 활용한다면 서버 사이드 렌더링의 양상은 완전히 달라집니다.

<Layout>
<NavBar />
<Sidebar />
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>

위의 예시에서 <Comments> 컴포넌트가 데이터 Fetching이 필요한 케이스라고 가정해 보겠습니다.

이제 <Suspense> 영역 외의 나머지 부분이 <Suspense>영역 컴포넌트에 영향받지 않고 즉시 스트리밍되며 렌더링 됩니다. 기존의 서버 사이드 렌더링의 장점 중 하나인, 사용자에게 빈 화면을 최대한 적게 보여주는 목적을 극대화할 수 있는 거죠.

위의 로딩 화면은 <Comments> 컴포넌트의 Data Fetching이 끝난 후, 서버에서 렌더링이 완료되면 대체됩니다.

기존 SSR의 첫 번째 문제인 특정 컴포넌트를 렌더링하는데 필요한 Data Fetching이 오래 걸린다면? 문제를 해결했습니다.

나머지 두 개의 문제를 살펴보겠습니다.

  • 특정 컴포넌트의 코드량이 커서 로딩이 오래 걸린다면?
  • 특정 컴포넌트가 복잡해서 렌더링하는 시간 자체가 오래 걸린다면?

이제 React.lazy를 다시 주목해야 할 때입니다.

React.lazy

React.lazy는 동적 import 를 사용하여 컴포넌트를 렌더링할 수 있게 해주는 함수입니다.

// lazy 컴포넌트
const OtherComponent = React.lazy(() => import('./OtherComponent'));

이러한 컴포넌트를 lazy 컴포넌트라고 하는데, 이 컴포넌트는 반드시 <Suspense> 컴포넌트 하위에서 렌더링되어야만 합니다.

import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}

지금까지는, 이 lazy 컴포넌트와 <Suspense>를 서버 사이드 렌더링에서 사용할 수 없었다는 것이 문제점이었습니다.

하지만 React 18부터는 새로운 렌더링 API인 pipeToNodeWritable 덕분에, <Suspense>와 함께 lazy 컴포넌트를 사용할 수 있게 되었습니다.

현재 리액트 생태계의 주류 환경인 웹팩 기반의 애플리케이션에서, lazy 컴포넌트를 사용하면 코드 스플리팅(Code Splitting)이 적용되어 별도의 자바스크립트 Chunk 파일로 분리됩니다. 그리고 이 <Suspense> 컴포넌트 하위 트리의 렌더링 외부 트리의 렌더링 과정을 막지 않고 별도의 과정이 진행됩니다.

아래의 예시를 보면, <Comments> 가 서버 사이드 렌더링의 몇 단계 과정에 있던 그것과 무관하게 바깥의 다른 컴포넌트들은 Hydration이 끝났습니다.

또한, React 18에서 Suspense 하위의 Hydration은 브라우저가 이벤트를 처리할 수 있도록 짧은 갭과 함께 진행됩니다. 덕분에 클릭은 즉시 처리되고 성능이 낮은 기기에서도 브라우저가 멈춘 것처럼 보이지 않게 되는 것이죠.

지금까지 React 18에서 적용한 새로운 아키텍처를 통해, 지금까지의 서버 사이드 렌더링과 관련된 문제점이 어떻게 해결할 수 있었는지 보았습니다. 이러한 개선을 통해 서버 사이드 렌더링을 하는 애플리케이션을 훨씬 더 나은 수준으로 개선할 수 있게 되었는데요.

여기에 한 가지 더, 생각지도 못한 놀라운 기능이 추가되었습니다.

이용자 친화적 Hydration

2개 이상의 lazy 컴포넌트를 서버 사이드 렌더링한다고 가정해 볼까요?

<Layout>
<NavBar />
<Suspense fallback={<Spinner />}>
<Sidebar />
</Suspense>
<RightPane>
<Post />
<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>
</RightPane>
</Layout>

이제 <Sidebar>컴포넌트와 <Comments> 컴포넌트는 나머지 일반 영역이 먼저 HTML로 렌더링되며 클라이언트로 스트리밍된 이후, 별도의 스트리밍이 시작됩니다.

먼저 두 개의 lazy 컴포넌트가 Data Fetching 이후 HTML로 렌더링 되며 스트리밍됩니다.
이때 다른 영역은 이미 Hydration까지 끝난 상태라고 가정해 보겠습니다.

그리고 나서, <Sidebar>컴포넌트와 <Comments> 컴포넌트의 Chunk 코드가 로딩됩니다. 코드 로딩이 완료되면 React는 두 컴포넌트 모두 Hydration을 시도하는데요. 컴포넌트 트리에서 더 먼저 발견된 Suspense 영역부터 Hydration을 시작합니다.

즉, 여기서는 <Sidebar> 컴포넌트가 먼저 Hydration이 진행됩니다.

이때 사용자가 코멘트 영역을 클릭한다고 가정해 보겠습니다.

그러면, 리액트는 이 클릭을 기록합니다. 그리고 <Comments> 컴포넌트에게 Hydration 우선순위를 높입니다. 왜냐하면, 사용자가 이 컴포넌트와 상호작용을 하고자 했으므로 더 긴급해졌기 때문이죠. 현재 Hydration이 진행 중이었던 <SideBar> 대신 <Comments> 컴포넌트를 먼저 Hydration 하게 됩니다.

<Comments> 영역의 Hydration이 완료되면, 리액트는 기록했던 클릭 이벤트를 다시 실행하여 이 컴포넌트에게 상호 작용에 반응하게끔 합니다. 예를 들어, 이용자가 Hydration 이전에 “코멘트 자세히 보기” 버튼을 클릭했었다면 Hydration이 끝난 후 “코멘트 자세히 보기" 버튼에 연결된 이벤트 핸들러를 실행하는 식입니다.

이제 리액트가 긴급하게 Hydration 해야 할 <Suspense> 영역이 없다면, 리액트는 <Sidebar>를 마저 Hydration 할 것입니다.

이렇게 React 18의 선택적 Hydration은 애플리케이션 전체의 Hydration 완료를 방해하지 않도록 별도의 Hydration을 진행합니다. 또한 선택적 Hydration의 대상이 여러 가지 일 때 사용자가 상호 작용하길 원하는(정확히는 상호 작용이 발생한) 영역이 있다면 앞서 진행하던 Hydration을 멈추고 사용자가 원하는 영역의 Hydration을 먼저 진행하는 놀라운 기능을 포함하고 있습니다.

3. React 18로 업그레이드 하기

React 18 업데이트에는 클라이언트와 서버 각각 Breaking Changes가 존재합니다.

3–1. 클라이언트

React 18부터 react-domrender 함수는 deprecated 됩니다.

// ~React 17
ReactDOM.render(<App />, container);

대신 새롭게 createRoot 함수를 사용합니다.

// React 18
const root = ReactDOM.createRoot(container);
root.render(<App />);

위에서 언급한 것처럼 이 변경은 단순하게 인터페이스만 바뀌는 것이 아닙니다.
기존의 render 함수는, 컴포넌트 트리를 React 17과 그 이전의 동작과 동일한 방식인 "레거시 모드"로 동작하도록 합니다.
새로운 createRoot 함수는, 컴포넌트 트리를 동시성(Concurrent) 기능들과 자동 배치 등 React 18의 기능들이 동작하도록 합니다.

그러므로 React 18과 앞으로 나올 새로운 리액트의 기능들을 사용하기 위해서는 반드시 ReactDOM.createRoot 함수를 사용하도록 애플리케이션을 변경해야 합니다.

3–2. 서버

React 18에는 렌더링을 위한 3가지 API가 존재합니다.

  • renderToString: 유지 (제한된 Suspense 지원)
  • renderToNodeStream: Deprecated (Full Suspense를 지원하나, 스트리밍되지 않음)
  • pipeToNodeWritable: 신규 API, 사용 추천(스트리밍으로 Full Suspense) 지원

만약 renderToNodeStream API를 사용하던 프로젝트라면, 이 API는 Deprecated 되었기 때문에 새로운 스트리밍 API인 pipeToNodeWritable로 대체해야 합니다.

또, renderToString을 사용하던 프로젝트라면 기존처럼 그대로 사용해도 됩니다. 하지만 React 18의 새로운 서버 사이드 렌더링 기능을 온전히 사용하고자 한다면, 마찬가지로 pipeToNodeWritable API로 변경하는 것이 좋을 것입니다.

--

--