React 19.2

TMT

https://react.dev/blog/2025/10/01/react-19-2

React 19.2가 이제 npm에서 사용할 수 있습니다!

이번 릴리스는 지난 해에 있었던 세 번째 릴리스로, 12월의 React 19와 6월의 React 19.1에 이어집니다. 이 글에서는 React 19.2의 새로운 기능 개요와 주목할 만한 변경 사항을 소개합니다.

새로운 React 기능

‎⁠<Activity />⁠

‎⁠<Activity>⁠는 앱을 “활동(activity)”으로 분할하고 이를 제어하고 우선순위를 지정할 수 있도록 해줍니다.

Activity를 앱의 일부를 조건부로 렌더링하는 대안으로 사용할 수 있습니다:

// Before
{isVisible && <Page />}

// After
<Activity mode={isVisible ? 'visible' : 'hidden'}>
  <Page />
</Activity>

React 19.2에서 Activity는 두 가지 모드를 지원합니다: ‎⁠visible⁠‎⁠hidden⁠.

  • hidden⁠: 자식을 숨기고, 이펙트를 언마운트하며, React가 처리할 작업이 더 이상 없을 때까지 모든 업데이트를 지연합니다.
  • ‎⁠visible⁠: 자식을 표시하고, 이펙트를 마운트하며, 업데이트가 정상적으로 처리되도록 허용합니다.

이는 화면에 표시되는 항목의 성능에 영향을 주지 않으면서 앱의 숨겨진 부분을 사전 렌더링하고 계속 렌더링할 수 있음을 의미합니다.

Activity를 사용하여 사용자가 다음으로 이동할 가능성이 높은 앱의 숨겨진 부분을 렌더링하거나, 사용자가 다른 곳으로 이동할 때 해당 부분의 상태를 저장할 수 있습니다. 이는 데이터, CSS, 이미지 등을 백그라운드에서 로드하여 탐색 속도를 높이고, 뒤로 가기 시 입력 필드와 같은 상태를 유지할 수 있도록 해줍니다.

앞으로, 다양한 사용 사례를 위한 더 많은 모드를 Activity에 추가할 계획입니다.

Activity 사용 예시는 Activity 문서를 확인하세요.

‎⁠useEffectEvent⁠

‎⁠useEffect⁠에서 흔한 패턴 중 하나는 외부 시스템으로부터의 어떤 “이벤트”를 앱 코드에 알리는 것입니다. 예를 들어, 채팅방이 연결되었을 때 알림을 표시하고 싶을 수 있습니다:

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => {
      connection.disconnect()
    };
  }, [roomId, theme]);
  // ...

위 코드의 문제는 그러한 “이벤트” 내부에서 사용되는 값이 변경될 때마다 주변 Effect가 다시 실행된다는 것입니다. 예를 들어, ‎⁠theme⁠를 변경하면 채팅방이 재연결됩니다. 이는 ‎⁠roomId⁠처럼 Effect 로직 자체와 관련된 값에 대해서는 타당하지만, ‎⁠theme⁠에 대해서는 타당하지 않습니다.

이를 해결하기 위해 대부분의 사용자는 린트 규칙을 비활성화하고 의존성을 제외합니다. 그러나 이렇게 하면 나중에 Effect를 업데이트해야 할 때 린터가 더 이상 의존성을 최신 상태로 유지하도록 도와줄 수 없어 버그로 이어질 수 있습니다.

‎⁠useEffectEvent⁠를 사용하면, 이 로직의 “이벤트” 부분을 이를 발생시키는 Effect로부터 분리할 수 있습니다:

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]); // ✅ 모든 의존성이 선언됨 (Effect Event는 의존성이 아님)
  // ...

DOM 이벤트와 유사하게, Effect Event는 항상 최신 props와 state를 “봅니다”.

Effect Event는 의존성 배열에 선언하면 안 됩니다. 린터가 Effect Event를 의존성으로 삽입하려 하지 않도록 ‎⁠eslint-plugin-react-hooks@latest⁠로 업그레이드해야 합니다. 또한 Effect Event는 “자신의” Effect와 동일한 컴포넌트 또는 Hook에서만 선언할 수 있습니다. 이러한 제약은 린터에 의해 검증됩니다.

[!Note] useEffectEvent⁠를 사용할 때

‎⁠useEffectEvent⁠는 개념적으로 “이벤트”인 함수이지만 사용자 이벤트 대신 Effect에서 발행되는 경우(그것이 “Effect Event”인 이유)에 사용해야 합니다. 모든 것을 ‎⁠useEffectEvent⁠로 감싸거나, 린트 에러를 잠재우기 위해서만 이를 사용하는 것은 버그를 유발할 수 있으므로 권장하지 않습니다.

Event Effect에 대해 사고하는 방법에 관한 심층적인 설명은: Separating Events from Effects를 참조하세요.

cacheSignal⁠

cacheSignalReact Server Components에서만 사용가능합니다.

‎⁠cacheSignal⁠‎⁠cache()⁠ 의 수명이 종료되었을 때를 알 수 있게 해줍니다:

import {cache, cacheSignal} from 'react';

const dedupedFetch = cache(fetch);

async function Component() {
  await dedupedFetch(url, { signal: cacheSignal() });
}

이를 통해 다음과 같은 경우, 캐시에서 더 이상 사용되지 않을 결과에 대한 정리(clean up) 또는 작업 중단(abort)을 수행할 수 있습니다:

  • React가 렌더링을 성공적으로 완료함
  • 렌더링이 중단됨
  • 렌더링이 실패함

자세한 정보는 ‎⁠cacheSignal⁠ 문서를 참조하세요.

Performance Tracks

React 19.2는 React 앱의 성능에 대한 더 많은 정보를 제공하기 위해 Chrome DevTools의 성능 프로파일에 새로운 커스텀 트랙 세트를 추가했습니다:

Image

React Performance Tracks 문서는 트랙에 포함된 모든 것을 설명하지만, 여기서는 고수준의 개요만 제공합니다.

Scheduler ⚛

Scheduler 트랙은 “사용자 상호작용을 위한 ‘blocking’” 또는 startTransition 내부 업데이트를 위한 “transition”과 같은 서로 다른 우선순위에 대해 React가 무엇을 작업하고 있는지 보여줍니다. 각 트랙 내부에서는 업데이트를 예약한 이벤트 유형과 해당 업데이트의 렌더가 언제 발생했는지와 같은 작업 유형을 볼 수 있습니다.

또한 업데이트가 다른 우선순위를 기다리느라 차단되었을 때, 또는 React가 계속 진행하기 전에 페인트를 기다릴 때 등의 정보도 표시합니다. Scheduler 트랙은 React가 코드를 서로 다른 우선순위로 어떻게 분할하는지, 그리고 작업을 완료한 순서를 이해하는 데 도움이 됩니다.

포함된 모든 항목을 확인하려면 Scheduler 트랙문서를 참조하세요.

Components ⚛

Components 트랙은 React가 렌더링하거나 이펙트를 실행하기 위해 작업 중인 컴포넌트 트리를 보여줍니다. 내부에는 자식이 마운트될 때 또는 이펙트가 마운트될 때의 “Mount” 라벨, React 외부 작업에 양보(yield)하느라 렌더링이 차단될 때의 “Blocked” 라벨 등을 볼 수 있습니다.

Components 트랙은 컴포넌트가 언제 렌더링되거나 이펙트를 실행하는지, 그리고 해당 작업을 완료하는 데 걸리는 시간을 이해하는 데 도움을 주어 성능 문제를 식별할 수 있게 해줍니다.

포함된 모든 항목을 확인하려면 Components 트랙 문서를 참조하세요.

새로운 React DOM 기능

Partial Pre-rendering

19.2에서는 앱의 일부를 미리 렌더링하고, 나중에 렌더링을 재개(resume)하는 새로운 기능을 추가했습니다.

이 기능은 “Partial Pre-rendering”이라고 하며, 앱의 정적 부분을 미리 렌더링하여 CDN에서 제공하고, 이후 셸(shell)을 렌더링을 재개하여 동적 콘텐츠로 채워 넣을 수 있도록 합니다.

나중에 재개할 앱을 사전 렌더링(pre-render)하려면 먼저 ‎⁠AbortController⁠와 함께 ‎⁠prerender⁠를 호출합니다:

const {prelude, postponed} = await prerender(<App />, {
  signal: controller.signal,
});

// Save the postponed state for later
await savePostponedState(postponed);

// Send prelude to client or CDN.

그런 다음, ‎⁠prelude⁠ 셸을 클라이언트로 반환하고, 나중에 ‎⁠resume⁠을 호출하여 SSR 스트림으로 “재개(resume)”할 수 있습니다:

const postponed = await getPostponedState(request);
const resumeStream = await resume(<App />, postponed);

// Send stream to client.

또는 SSG를 위한 정적 HTML을 얻기 위해 ‎⁠resumeAndPrerender⁠를 호출할 수 있습니다:

const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(<App />, postponedState);

// Send complete HTML prelude to CDN.

새 API에 대한 자세한 정보는 다음 문서를 참조하세요:

또한 prerender API는 이제 ‎⁠resume⁠ API로 전달할 ‎⁠postpone⁠ 상태를 반환합니다.

주목할 만한 변경 사항

SSR의 Suspense 경계 일괄 처리(Batching)

서버 사이드 렌더링 스트리밍 시와 클라이언트에서 렌더링될 때 Suspense 경계가 서로 다르게 드러나는(reveal) 동작상의 버그를 수정했습니다.

19.2부터 React는 서버에서 렌더링된 Suspense 경계의 드러남을 짧은 시간 동안 일괄 처리하여, 더 많은 콘텐츠가 함께 드러나고 클라이언트 렌더링된 동작과 일치하도록 합니다.

Image

이전에는 스트리밍 서버 사이드 렌더링 중에 서스펜스 콘텐츠가 즉시 폴백을 대체했습니다.

Image

React 19.2에서는 더 많은 콘텐츠를 함께 드러낼 수 있도록, 서스펜스 경계가 짧은 시간 동안 배치되어 처리됩니다.

이 수정은 또한 SSR 중 Suspense에 대한 ‎⁠<ViewTransition>⁠ 지원을 준비합니다. 더 많은 콘텐츠를 함께 드러냄으로써, 애니메이션을 더 큰 콘텐츠 배치에서 실행할 수 있고, 서로 가까운 시간에 스트리밍되는 콘텐츠의 애니메이션이 연쇄적으로 이어지는 것을 피할 수 있습니다.

[!Note] React는 스로틀링이 핵심 웹 바이탈(core web vital)과 검색 순위에 영향을 미치지 않도록 휴리스틱을 사용합니다.

예를 들어, 전체 페이지 로드 시간이 2.5초에 가까워지는 경우(이는 LCP 기준으로 “좋음”으로 간주되는 시간), React는 배칭을 중단하고 콘텐츠를 즉시 드러내며, 스로틀링이 메트릭 누락의 원인이 되지 않도록 합니다.

SSR: Node용 Web Streams 지원

React 19.2는 Node.js에서의 스트리밍 SSR을 위해 Web Streams 지원을 추가했습니다:

새로운 ‎⁠resume⁠ API도 추가되었습니다:

[!Pitfall] Node.js에서 서버 사이드 렌더링에는 Node Streams를 선호하세요

Node.js 환경에서는 여전히 Node Streams API 사용을 강력히 권장합니다:

이는 Node Streams가 Node에서 Web Streams보다 훨씬 빠르고, Web Streams는 기본적으로 압축을 지원하지 않아 스트리밍의 이점을 놓치는 경우가 발생하기 때문입니다.

‎⁠eslint-plugin-react-hooks⁠ v6

‎⁠eslint-plugin-react-hooks@latest⁠도 게시했으며, ‎⁠recommended⁠ 프리셋에서 기본적으로 flat config를 사용하고 새로운 React Compiler 기반 규칙을 옵트인으로 제공합니다.

레거시 설정을 계속 사용하려면 ‎⁠recommended-legacy⁠로 변경할 수 있습니다:

- extends: ['plugin:react-hooks/recommended']
+ extends: ['plugin:react-hooks/recommended-legacy']

컴파일러가 활성화한 규칙의 전체 목록은 linter 문서를 확인하세요.

기본 ‎⁠useId⁠ 프리픽스 업데이트

19.2에서는 기본 ‎⁠useId⁠ 프리픽스를 ‎⁠:r:⁠(19.0.0) 또는 ‎⁠«r»⁠(19.1.0)에서 ‎⁠_r_⁠로 업데이트합니다.

CSS 선택자에 유효하지 않은 특수 문자를 사용함으로써 사용자 작성 ID와의 충돌 가능성을 낮추는 것이 원래 의도였습니다. 그러나 View Transitions를 지원하려면, ‎⁠useId⁠가 생성하는 ID가 ‎⁠view-transition-name⁠ 및 XML 1.0 이름에 대해 유효해야 합니다.

변경 로그

전체 변경 사항 목록은 Changelog를 참조하세요.

Edit this page