React 19.2

I
Inkyu Oh

Front-End2025.11.16



2025년 10월 1일 작성자: React 팀


React 19.2가 npm에서 이용 가능합니다!
이것은 지난 해의 세 번째 릴리스로, 12월의 React 19와 6월의 React 19.1에 이어집니다. 이 포스트에서는 React 19.2의 새로운 기능을 개요하고 주목할 만한 변경 사항들을 강조하겠습니다.



새로운 React 기능

<Activity />

<Activity>를 사용하면 앱을 제어하고 우선순위를 지정할 수 있는 "활동"으로 나눌 수 있습니다.
앱의 일부를 조건부로 렌더링하는 대신 Activity를 사용할 수 있습니다:
// Before
{isVisible && <Page />}

// After
<Activity mode={isVisible ? 'visible' : 'hidden'}>
<Page />
</Activity>
React 19.2에서 Activity는 두 가지 모드를 지원합니다: visiblehidden.
  • hidden: 자식을 숨기고, effect를 언마운트하며, React가 할 일이 남아있지 않을 때까지 모든 업데이트를 연기합니다.
  • visible: 자식을 표시하고, effect를 마운트하며, 업데이트를 정상적으로 처리합니다.
이는 화면에 보이는 것의 성능에 영향을 주지 않으면서 앱의 숨겨진 부분을 사전 렌더링하고 계속 렌더링할 수 있다는 의미입니다.
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에는 의미가 없습니다.
이를 해결하기 위해 대부분의 사용자는 lint 규칙을 비활성화하고 의존성을 제외합니다. 하지만 나중에 Effect를 업데이트해야 할 경우 linter가 더 이상 의존성을 최신 상태로 유지하도록 도와줄 수 없으므로 버그로 이어질 수 있습니다.
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 Events는 의존성이 아님)
// ...
}
DOM 이벤트와 유사하게 Effect Events는 항상 최신 props와 state를 "봅니다".
Effect Events는 의존성 배열에 선언되어서는 안 됩니다. eslint-plugin-react-hooks@latest로 업그레이드해야 linter가 이들을 의존성으로 삽입하려고 시도하지 않습니다. Effect Events는 "자신의" Effect와 동일한 컴포넌트 또는 Hook에서만 선언될 수 있습니다. 이러한 제한 사항은 linter에 의해 검증됩니다.
useEffectEvent를 사용할 때 Effect 대신 사용자 이벤트에서 발생하는 개념적으로 "이벤트"인 함수에 useEffectEvent를 사용해야 합니다 (이것이 "Effect Event"를 만드는 것입니다). lint 오류를 침묵시키기 위해 모든 것을 useEffectEvent로 감싸거나 사용할 필요는 없습니다. 이는 버그로 이어질 수 있습니다. Event Effects를 생각하는 방법에 대한 심층 분석은 이벤트와 Effect 분리하기를 참조하세요.



cacheSignal

cacheSignal을 사용하면 cache() 수명이 끝날 때를 알 수 있습니다:
import { cache, cacheSignal } from 'react';

const dedupedFetch = cache(fetch);

async function Component() {
await dedupedFetch(url, { signal: cacheSignal() });
}
이를 통해 결과가 더 이상 캐시에서 사용되지 않을 때 작업을 정리하거나 중단할 수 있습니다. 예를 들어:
  • React가 렌더링을 성공적으로 완료했을 때
  • 렌더링이 중단되었을 때
  • 렌더링이 실패했을 때
자세한 내용은 cacheSignal 문서를 참조하세요.



Performance Tracks

React 19.2는 React 앱의 성능에 대한 더 많은 정보를 제공하기 위해 Chrome DevTools 성능 프로필에 새로운 사용자 정의 트랙 세트를 추가합니다:
React Performance Tracks 문서는 트랙에 포함된 모든 것을 설명하지만, 여기에 높은 수준의 개요가 있습니다.

Scheduler ⚛

Scheduler 트랙은 React가 "blocking" (사용자 상호작용) 또는 "transition" (startTransition 내 업데이트)과 같은 다양한 우선순위에 대해 작업 중인 것을 보여줍니다. 각 트랙 내에서 수행 중인 작업의 유형 (예: 업데이트를 예약한 이벤트) 및 해당 업데이트의 렌더링이 발생한 시기를 볼 수 있습니다.
또한 업데이트가 다른 우선순위를 기다리며 차단되었을 때 또는 React가 계속하기 전에 페인트를 기다리고 있을 때와 같은 정보도 표시합니다. Scheduler 트랙은 React가 코드를 다양한 우선순위로 어떻게 분할하는지, 그리고 작업을 완료한 순서를 이해하는 데 도움이 됩니다.
Scheduler 트랙 문서를 참조하여 포함된 모든 것을 확인하세요.

Components ⚛

Components 트랙은 React가 렌더링하거나 effect를 실행하기 위해 작업 중인 컴포넌트 트리를 보여줍니다. 내부에서 자식이 마운트되거나 effect가 마운트될 때의 "Mount" 또는 React 외부의 작업에 양보하여 렌더링이 차단될 때의 "Blocked"와 같은 레이블을 볼 수 있습니다.
Components 트랙은 컴포넌트가 렌더링되거나 effect를 실행할 때와 해당 작업을 완료하는 데 걸리는 시간을 이해하는 데 도움이 되어 성능 문제를 식별하는 데 도움이 됩니다.
Components 트랙 문서를 참조하여 포함된 모든 것을 확인하세요.



새로운 React DOM 기능

Partial Pre-rendering

19.2에서는 앱의 일부를 미리 렌더링하고 나중에 렌더링을 재개할 수 있는 새로운 기능을 추가하고 있습니다.
이 기능을 "Partial Pre-rendering"이라고 하며, 앱의 정적 부분을 사전 렌더링하고 CDN에서 제공한 다음 나중에 셸을 렌더링하여 동적 콘텐츠로 채울 수 있습니다.
나중에 재개하기 위해 앱을 사전 렌더링하려면 먼저 AbortController와 함께 prerender를 호출하세요:
const { prelude, postponed } = await prerender(<App />, {
signal: controller.signal,
});
// 나중을 위해 postponed 상태 저장
await savePostponedState(postponed);
// prelude를 클라이언트 또는 CDN으로 전송합니다.
그런 다음 prelude 셸을 클라이언트에 반환하고 나중에 resume을 호출하여 SSR 스트림으로 "재개"할 수 있습니다:
const postponed = await getPostponedState(request);
const resumeStream = await resume(<App />, postponed);
// 스트림을 클라이언트로 전송합니다.
또는 resumeAndPrerender를 호출하여 SSG를 위한 정적 HTML을 얻기 위해 재개할 수 있습니다:
const postponedState = await getPostponedState(request);
const { prelude } = await resumeAndPrerender(<App />, postponedState);
// 완전한 HTML prelude를 CDN으로 전송합니다.
자세한 내용은 새로운 API에 대한 문서를 참조하세요:
  • react-dom/server
  • react-dom/static
또한 prerender API는 이제 resume API에 전달할 postpone 상태를 반환합니다.



주목할 만한 변경 사항

SSR을 위한 Suspense Boundaries 배칭

Suspense boundaries가 클라이언트에서 렌더링되는지 또는 서버 측 렌더링에서 스트리밍되는지에 따라 다르게 표시되는 동작 버그를 수정했습니다.
19.2부터 React는 서버 렌더링된 Suspense boundaries의 표시를 짧은 시간 동안 배칭하여 더 많은 콘텐츠를 함께 표시할 수 있도록 하고 클라이언트 렌더링 동작과 정렬합니다.
이전: 스트리밍 서버 측 렌더링 중에 suspense 콘텐츠가 즉시 fallback을 대체합니다.

이후: React 19.2에서 suspense boundaries는 짧은 시간 동안 배칭되어 더 많은 콘텐츠를 함께 표시할 수 있습니다.

이 수정은 또한 SSR 중 Suspense에 대한 <ViewTransition> 지원을 위해 앱을 준비합니다. 더 많은 콘텐츠를 함께 표시함으로써 애니메이션은 더 큰 콘텐츠 배치에서 실행될 수 있으며, 가까이 스트리밍되는 콘텐츠의 애니메이션 연쇄를 피할 수 있습니다.
참고 React는 휴리스틱을 사용하여 throttling이 핵심 웹 지표 및 검색 순위에 영향을 주지 않도록 합니다. 예를 들어, 전체 페이지 로드 시간이 2.5초에 가까워지면 (LCP에 대해 "좋음"으로 간주되는 시간), React는 배칭을 중지하고 throttling이 메트릭을 놓치는 이유가 되지 않도록 콘텐츠를 즉시 표시합니다.



SSR: Node를 위한 Web Streams 지원

React 19.2는 Node.js에서 스트리밍 SSR을 위한 Web Streams 지원을 추가합니다:
  • prerender은 이제 Node.js에서 사용 가능합니다
그리고 새로운 resume API:
  • resume은 Node.js에서 사용 가능합니다.



eslint-plugin-react-hooks v6

또한 recommended 프리셋에서 기본적으로 flat config를 사용하고 새로운 React Compiler 기반 규칙에 대해 opt-in하는 eslint-plugin-react-hooks@latest를 발행했습니다.
레거시 config를 계속 사용하려면 recommended-legacy로 변경할 수 있습니다:
- extends: ['plugin:react-hooks/recommended']
+ extends: ['plugin:react-hooks/recommended-legacy']
컴파일러 활성화 규칙의 전체 목록은 linter 문서를 확인하세요.
eslint-plugin-react-hooks 변경 로그에서 변경 사항의 전체 목록을 확인하세요.



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 이름에 유효한지 확인해야 합니다.



변경 로그

기타 주목할 만한 변경 사항
  • react-dom: hoistable 스타일에서 nonce 사용 허용 #32461
  • react-dom: React 소유 노드를 텍스트 콘텐츠도 있는 Container로 사용하는 경우 경고 #32774
주목할 만한 버그 수정
  • react: Context를 "SomeContext.Provider" 대신 "SomeContext"로 문자열화 #33507
  • react: popstate 이벤트에서 무한 useDeferredValue 루프 수정 #32821
  • react: useDeferredValue에 초기값이 전달된 경우 버그 수정 #34376
  • react: Client Actions로 양식을 제출할 때 충돌 수정 #33055
  • react: 재중단되는 경우 dehydrated suspense boundaries의 콘텐츠 숨기기/표시 #32900
  • react: Hot Reload 중 넓은 트리에서 스택 오버플로우 방지 #34145
  • react: React.lazy-ed Component 내부의 React.use 버그 수정 #33941
  • react-dom: ARIA 1.3 속성이 사용될 때 경고 중지 #34264
  • react-dom: Suspense fallback 내부의 깊게 중첩된 Suspense 버그 수정 #33467
  • react-dom: 렌더링 중 중단 후 중단될 때 행(hang) 방지 #34192
변경 사항의 전체 목록은 변경 로그를 참조하세요.


이 포스트를 작성한 Ricky HanlonDan Abramov, Matt Carroll, Jack Pope, Joe Savona에게 감사합니다.
1
56

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!