Contents
see List개요
React 19는 서버 중심 아키텍처를 완성하는 중대한 릴리스입니다. 새로운 use() 훅, Actions 시스템, 향상된 서버 컴포넌트, 개선된 폼 처리 등 프론트엔드 개발의 패러다임을 바꾸는 기능들이 도입되었습니다. 기존 React 개발 경험을 크게 개선하면서도 점진적으로 도입할 수 있도록 설계된 React 19의 핵심 기능을 살펴봅니다.
핵심 개념
1. use() 훅: Promise와 Context를 컴포넌트 내에서 직접 읽을 수 있는 새로운 API입니다. 기존 useEffect + useState 패턴을 대체하며, 조건문 안에서도 호출할 수 있는 최초의 React 훅입니다.
2. Actions: 폼 제출과 데이터 변경을 처리하는 새로운 패턴입니다. useActionState, useFormStatus, useOptimistic 등의 훅과 함께 서버 액션 또는 클라이언트 액션으로 구현할 수 있습니다.
3. ref의 변화: forwardRef가 더 이상 필요하지 않습니다. ref가 일반 prop으로 전달되며, 정리(cleanup) 함수를 반환할 수 있습니다.
4. 문서 메타데이터: title, meta, link 태그를 컴포넌트 내에서 직접 렌더링하면 자동으로 document head로 호이스팅됩니다. react-helmet 같은 라이브러리가 불필요해졌습니다.
5. 스타일시트 지원: 컴포넌트가 CSS 파일을 선언적으로 로드할 수 있으며, React가 로드 순서와 중복 방지를 자동으로 관리합니다.
실전 예제
use() 훅으로 비동기 데이터를 처리하는 예시입니다.
// use() 훅 - Promise를 직접 읽기
import { use, Suspense } from 'react';
// 캐싱된 fetch 함수
function fetchUser(id: number): Promise<User> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
// use()는 Promise를 직접 읽음 - Suspense와 자동 통합
const user = use(userPromise);
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// 조건부 use() - 기존 훅에서는 불가능
function ConditionalData({ showDetails, dataPromise }) {
if (showDetails) {
const data = use(dataPromise); // 조건문 안에서 사용 가능
return <Details data={data} />;
}
return <Summary />;
}
// 부모 컴포넌트
function App() {
const userPromise = fetchUser(1); // 렌더링 시 Promise 생성
return (
<Suspense fallback={<div>로딩 중...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
Actions와 useOptimistic을 활용한 폼 처리입니다.
// Actions + useOptimistic + useActionState
'use client';
import { useActionState, useOptimistic, useRef } from 'react';
interface Comment {
id: number;
text: string;
}
async function addComment(prev: Comment[], formData: FormData) {
'use server';
const text = formData.get('text') as string;
await db.comments.create({ data: { text } });
return await db.comments.findMany();
}
function CommentForm({ comments }: { comments: Comment[] }) {
const [state, formAction, isPending] = useActionState(addComment, comments);
const [optimistic, addOptimistic] = useOptimistic(
state,
(current, newText: string) => [
...current,
{ id: Date.now(), text: newText },
]
);
const formRef = useRef<HTMLFormElement>(null);
return (
<div>
{optimistic.map(c => <p key={c.id}>{c.text}</p>)}
<form ref={formRef} action={async (formData) => {
const text = formData.get('text') as string;
addOptimistic(text);
formRef.current?.reset();
await formAction(formData);
}}>
<input name="text" required />
<button disabled={isPending}>
{isPending ? '저장 중...' : '댓글 작성'}
</button>
</form>
</div>
);
}
활용 팁
- use()는 Suspense와 함께 사용하세요. Promise가 resolve될 때까지 Suspense fallback이 표시되므로, 적절한 로딩 UI를 제공해야 합니다.
- useFormStatus를 활용하면 폼의 pending 상태를 하위 컴포넌트에서 쉽게 접근할 수 있습니다. 버튼 비활성화, 로딩 인디케이터 등에 유용합니다.
- ref cleanup 함수를 활용하여 DOM 노드가 제거될 때 리소스를 정리하세요. 이전 null 체크 패턴보다 직관적입니다.
- document metadata 호이스팅을 활용하면 각 페이지 컴포넌트에서 SEO 메타 태그를 직접 관리할 수 있어 코드 응집도가 높아집니다.
- React 19로 업그레이드 시 React Compiler(이전 React Forget)를 함께 도입하면 수동 메모이제이션(useMemo, useCallback, memo)을 제거할 수 있습니다.
마무리
React 19는 서버-클라이언트 통합, 비동기 데이터 처리, 폼 관리 등 웹 개발의 핵심 과제를 프레임워크 레벨에서 해결합니다. use() 훅, Actions, 서버 컴포넌트는 각각 독립적이면서도 서로 시너지를 내는 기능들입니다. 점진적으로 도입 가능하므로, 기존 프로젝트에서도 부담 없이 새 기능의 이점을 누릴 수 있습니다.