왜 이 시리즈인가
React 18이 2022년 3월에 출시되고, React 19가 2024년 12월에 나왔습니다. 그리고 19.1, 19.2를 거치며 2026년 현재 19.2.1이 최신 안정 버전입니다. 약 3년간의 변화는 React의 역사에서 가장 큰 패러다임 전환 중 하나로 기록될 만합니다.
이 시리즈는 그 변화를 정리합니다. 단순히 "어떤 API가 추가되었다"를 나열하는 것이 아니라, 각 변화가 왜 필요했고, 어떻게 사용해야 베스트 프랙티스인지 를 다룹니다. 7편에 걸쳐 살펴볼 내용은 다음과 같습니다.
- 이 글 , 전체 변화의 큰 그림
- Concurrent Rendering의 본질,
useTransition,useDeferredValue, Suspense - 외부 상태와 동기화,
useSyncExternalStore와 tearing - Actions와 폼 처리,
useActionState,useFormStatus,useOptimistic - Server Components와
use(), RSC와 새로운 데이터 패칭 ref와 Context의 변화,forwardRef의 종말- 19.1과 19.2의 새 기능, Activity, View Transitions, 메타데이터
React 18, Concurrent의 시대를 열다
React 18 (2022.03) 의 핵심은 Concurrent Rendering 입니다. 이전까지 React의 렌더링은 시작되면 끝까지 동기적으로 진행되는 것이었습니다. 한번 렌더링이 시작되면 메인 스레드를 점유하여, 큰 트리를 렌더링하는 동안 사용자 입력에 반응할 수 없었습니다.
Concurrent Rendering은 이 모델을 바꿨습니다. React는 렌더링을 중간에 멈추고, 다시 시작하고, 우선순위를 조정 할 수 있게 되었습니다. 사용자 입력 같은 긴급한 작업은 끼어들 수 있고, 무거운 렌더링은 백그라운드에서 진행됩니다.
이 변화 위에 새로운 Hook과 API들이 등장했습니다.
React 18의 새 기능들
useTransition , 상태 업데이트를 "긴급하지 않은 것"으로 표시합니다. React는 이 업데이트를 백그라운드에서 처리하고, 그 사이에 새로운 긴급한 업데이트가 들어오면 우선 처리합니다.
useDeferredValue , 값의 업데이트를 지연시킵니다. 비싼 렌더링이 입력 응답성을 망치는 것을 막을 때 사용합니다.
useId , SSR과 클라이언트 간에 안전하게 일치하는 고유 ID를 생성합니다. 접근성 속성 (aria-labelledby 등) 에 자주 사용됩니다.
useSyncExternalStore , 외부 스토어 (Zustand, Redux 등) 를 React의 렌더링과 안전하게 동기화합니다. Concurrent Rendering이 도입한 tearing 문제를 해결하기 위해 만들어졌습니다.
useInsertionEffect , CSS-in-JS 라이브러리를 위한 특수 Hook입니다. DOM 변경 전에 스타일을 주입할 수 있게 해줍니다.
Automatic Batching , React 18 이전에는 Promise, setTimeout, 네이티브 이벤트 핸들러 안의 state 업데이트가 배치되지 않았습니다. 18부터는 모든 곳에서 자동으로 배치됩니다.
Suspense for SSR , 서버 사이드 렌더링에서도 Suspense를 사용할 수 있습니다. 페이지의 일부가 준비되지 않아도 나머지를 먼저 보낼 수 있습니다 (Streaming SSR).
React 19, 패러다임의 전환
React 19 (2024.12) 는 React 18이 깔아둔 토대 위에서 데이터 처리 방식 자체 를 바꿨습니다. 핵심은 두 가지입니다, Server Components의 안정화와 Actions의 도입 .
Server Components
Server Components는 서버에서만 실행되는 컴포넌트입니다. 데이터베이스에 직접 접근하고, 환경 변수를 읽고, 서버 전용 라이브러리를 사용할 수 있습니다. 그 결과 HTML과 함께 직렬화되어 클라이언트로 전송되며, 클라이언트 번들에는 포함되지 않습니다.
이전에는 데이터를 가져오려면 클라이언트에서 useEffect를 쓰거나, 별도의 데이터 패칭 라이브러리 (TanStack Query 등) 를 사용해야 했습니다. Server Components는 이 패턴을 컴포넌트 자체가 데이터를 직접 가져오는 방식으로 바꿉니다.
// Server Component
async function UserProfile({ userId }) {
const user = await db.users.findById(userId); // 직접 DB 접근
return <div>{user.name}</div>;
}Actions
Actions는 폼 제출과 같은 mutation을 다루는 새로운 패턴입니다. 이전에는 폼 제출 시 로딩 상태, 에러 상태, 낙관적 업데이트를 모두 수동으로 관리해야 했습니다. Actions는 이를 React 자체의 기능으로 통합했습니다.
function UpdateForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const result = await updateUser(formData);
return { error: result.error ?? null };
},
{ error: null }
);
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>저장</button>
{state.error && <p>{state.error}</p>}
</form>
);
}useActionState, useFormStatus, useOptimistic, 세 가지 Hook이 Actions 패러다임의 핵심입니다.
use() Hook
use()는 React 19의 가장 흥미로운 추가 중 하나입니다. Promise나 Context를 조건문 안에서도 읽을 수 있게 해주는 특수한 함수입니다.
function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // Promise를 직접 읽음
return comments.map(c => <p key={c.id}>{c.text}</p>);
}기존 Hook들은 컴포넌트 최상단에서만 호출할 수 있다는 규칙이 있었습니다. use()는 그 규칙에서 자유롭습니다. 조건문이나 반복문 안에서도 사용 가능합니다.
그 외 19의 변화
ref를 일반 prop으로 ,forwardRef없이 함수 컴포넌트가ref를 받을 수 있습니다<Context>as Provider ,<MyContext.Provider>대신<MyContext>만으로 충분합니다- Document Metadata , 컴포넌트 안에서
<title>,<meta>를 직접 사용하면 React가 자동으로<head>로 옮겨줍니다 - Asset Preloading ,
preload,preinit같은 API로 리소스를 미리 로드할 수 있습니다
React 19.1, 19.2, 안정화와 새 기능
19.0이 큰 패러다임 전환이었다면, 19.1과 19.2는 그 위에 작지만 의미 있는 기능을 추가했습니다.
React 19.1 (2025.03)
- Owner Stacks ,
captureOwnerStackAPI를 통해 어떤 컴포넌트가 다른 컴포넌트의 렌더링을 "소유"했는지를 추적하는 새로운 디버깅 기능 - 에러 보고 메커니즘 개선
- Server Components 디버깅 향상
React 19.2 (2025.10)
- Activity API , 컴포넌트를 "비활성 상태"로 두고 상태를 유지할 수 있는 API입니다. 탭 전환 같은 상황에서 컴포넌트를 unmount하지 않고 숨길 수 있습니다
- View Transitions (실험적) , 브라우저의 View Transitions API를 React 렌더링과 통합하기 위한 작업이 진행 중이며, 19.2 시점에서는 Canary/Experimental 채널에서 사용 가능합니다
- Performance Tracks , Chrome DevTools의 Performance 패널에 React의 렌더링 작업을 표시합니다
시리즈 진행 방향
다음 글부터는 각 주제를 깊이 들여다봅니다. 단순히 API 사용법이 아니라, 왜 이 API가 필요했는지 와 언제 어떻게 써야 베스트 프랙티스인지 를 함께 다룹니다.
다음 글에서는 Concurrent Rendering의 본질을 살펴봅니다. useTransition과 useDeferredValue가 정확히 무엇을 다르게 하는지, Suspense가 왜 데이터 패칭과 결합되는지, 그리고 이 모든 것이 어떻게 한 줄기로 연결되는지를 정리합니다.