Justin-book

React v19 정리

2025-10-16

React 19 주요 변경사항 정리 (Stable)

요약 표

범주주요 변경점관련 API / 특징
데이터 변경 흐름Actions (비동기 전환과 결합)useTransition 기반 async 전환, <form action={fn}>, 자동 pending/에러/리셋
폼 상태useActionState / useFormStatus액션 결과/대기 상태 반환, 디자인 컴포넌트에서 상위 <form> 상태 접근
낙관적 UIuseOptimistic요청 중 최종 상태를 미리 보여주고 실패 시 자동 롤백
데이터 읽기use렌더 중 Promise/Context를 읽고 자동 Suspend (조건부 호출 가능)
메타데이터Document Metadata<title>, <meta>, <link>를 컴포넌트 안에서 선언하면 자동으로 <head>로 호이스팅
스타일Stylesheet precedence<link rel="stylesheet" precedence="...">로 삽입 순서/로딩 제어
호환성/개선ref as prop, <Context> 제공자함수 컴포넌트에서 ref 직접 사용, <Context.Provider> 대신 <Context> 사용 가능
웹 컴포넌트Custom Elements 지원 강화속성/프로퍼티/이벤트 처리 개선(SSR 포함)
정적 출력React DOM Static APIsreact-dom/staticprerender, prerenderToNodeStream
성능/개선Suspense 사전 웜업, Hydration 오류 Diff, useDeferredValue(initialValue)개발자 경험/성능 개선

— 공식 변경점: React 팀 블로그(Dec 5, 2024), React 19.2(Oct 1, 2025) 참조.

  • React 19 안정화 공지 및 기능 요약 (2024‑12‑05), 정적 API/서버 컴포넌트/Actions/메타데이터 등
  • React 19.2 릴리스 노트 (2025‑10‑01) – 추가 개선 사항
업그레이드 팁
  1. react/react-dom을 19로 올린 후, 빌드/테스트
  2. 폼 처리/데이터 변경 로직에 Actions 적용 고려
  3. react-helmet 등 메타 라이브러리는 Document Metadata와 병행 사용 가능
  4. 컴포넌트에서 forwardRef를 새로 도입하는 대신 ref as prop 패턴 사용 검토
  5. 정적 사이트는 react-dom/static API로 SSG 경로를 점검

Breaking changes와 마이그레이션은 공식 Upgrade Guide 참조


1) Actions: 폼·데이터 변경의 표준화

  • 비동기 전환(startTransition) 안에서 대기/에러 처리를 자동화
  • <form>함수action/formAction으로 전달 → 제출/리셋 자동화
  • 낙관적 업데이트(useOptimistic)·에러 바운더리와 자연스럽게 연동
  • useActionState: 액션 래핑 → [result, submitAction, pending] 형태로 사용

코드 예시는 react19.jsChangeName/SubmitButton 참고


async function updateNameOnServer(name) {
  // 데모용 가짜 API
  await new Promise((r) => setTimeout(r, 800));
  if (!name) return '이름은 필수입니다.';
  return null; // 에러 없음
}

function SubmitButton() {
  // 부모 <form>의 pending 상태에 접근
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '저장 중…' : '저장'}
    </button>
  );
}

function ChangeName() {
  // 낙관적 UI
  const [name, setOptimisticName] = useOptimistic('Josh');
  // 액션 래핑 (이전 useFormState → React 19에서 useActionState)
  const [error, submitAction, isPending] = useActionState(
    async (_prev, formData) => {
      const newName = formData.get('name');
      setOptimisticName(newName); // 낙관적 업데이트
      const err = await updateNameOnServer(newName);
      if (err) return err; // 오류 시 에러를 반환하면 자동으로 롤백됨
      // 성공 후 라우팅/리프레시 등
      // redirect('/profile'); // 프레임워크에 따라 사용
      return null;
    },
    null
  );

  return (
    <form action={submitAction}>
      <input name="name" defaultValue={name} />
      <SubmitButton />
      {isPending && <p>서버 반영 중…</p>}
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

2) 폼 훅: useActionState / useFormStatus

  • useActionState(action, initialState) → 액션 호출기와 결과/대기 상태를 제공
  • 디자인 시스템에서 <form> 상태(예: pending)를 하위 버튼이 필요할 때 useFormStatus()로 쉽게 접근
function SubmitButton() {
  // 부모 <form>의 pending 상태에 접근
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? '저장 중…' : '저장'}
    </button>
  );
}


3) use(): 렌더 중 비동기 자원 소비

  • use(promise)해결될 때까지 Suspense (조건부로도 호출 가능)
  • use(Context) → 조기 반환(early return)에서도 안전하게 컨텍스트 읽기
function fetchComments() {
  return new Promise((resolve) =>
    setTimeout(() => resolve([{ id: 1, text: 'React 19 👍' }]), 700)
  );
}
const commentsPromise = fetchComments();

const ThemeContext = createContext({ color: 'teal' });

function Comments() {
  // use()는 Promise가 해결될 때까지 컴포넌트를 Suspend
  const comments = use(commentsPromise);
  return (
    <ul>
      {comments.map((c) => (
        <li key={c.id}>{c.text}</li>
      ))}
    </ul>
  );
}

function ThemedTitle({ children }) {
  // use(Context) — early return 구조에서도 안전
  const theme = use(ThemeContext);
  return <h2 style={{ color: theme.color }}>{children}</h2>;
}


4) Document Metadata & Stylesheet

  • 컴포넌트 안에 <title>, <meta>, <link>를 선언하면 **자동으로 <head>**로 이동
  • <link rel="stylesheet" precedence="high|default|low">삽입 순서·로딩 제어

function Article({ title, keywords }) {
  return (
    <article>
      <title>{title}</title>
      <meta name="keywords" content={keywords.join(',')} />
      <link rel="stylesheet" href="/article.css" precedence="default" />
      <h3>{title}</h3>
      <p>문서 메타데이터와 스타일시트를 컴포넌트 내부에서 선언합니다.</p>
    </article>
  );
}


5) 호환성 개선: ref as prop / <Context> 제공자

  • 함수 컴포넌트에서 ref를 prop으로 직접 받기 → forwardRef 점진적 대체 예정
  • <Context.Provider> 대신 <Context>제공자로 직접 렌더링 가능

6) Web Components (Custom Elements)

  • 리액트 19에서 프로퍼티/이벤트 전달이 일관되게 동작 (SSR 포함)
  • 예: <my-counter value={5} onincrement={...} />
function MyInput({ placeholder, ref }) {
  // 함수 컴포넌트에서 forwardRef 없이 ref prop 사용 (React 19)
  return <input placeholder={placeholder} ref={ref} />;
}

function CustomElementsDemo() {
  const ref = useRef();
  // 커스텀 엘리먼트에 프로퍼티/이벤트 전달
  return (
    <div>
      <my-counter
        value={5}
        onincrement={(e) => console.log('increment', e.detail)}
      />
      <MyInput placeholder="ref as prop" ref={ref} />
    </div>
  );
}

function App() {
  const [q, setQ] = useState('');
  // useDeferredValue 초기값 (initialValue) 옵션 사용
  const deferred = useDeferredValue(q, '');

  return (
    <ThemeContext value={{ color: 'purple' /* <Context> as provider */ }}>
      <ThemedTitle>React 19 데모</ThemedTitle>

      <section>
        <h4>① Actions 폼</h4>
        <ChangeName />
      </section>

      <section>
        <h4>② use() + Suspense</h4>
        <Suspense fallback={<p>댓글 불러오는 중…</p>}>
          <Comments />
        </Suspense>
      </section>

      <section>
        <h4>③ Document Metadata / Stylesheet</h4>
        <Article title="React 19 메타데이터" keywords={['react', 'v19']} />
      </section>

      <section>
        <h4>④ Custom Elements & ref as prop</h4>
        <CustomElementsDemo />
      </section>

      <section>
        <h4>⑤ useDeferredValue(initialValue)</h4>
        <input value={q} onChange={(e) => setQ(e.target.value)} />
        <p>검색어(지연 값): {deferred}</p>
      </section>
    </ThemeContext>
  );
}

root.render(<App />);


7) React DOM Static APIs (SSG)

  • react-dom/staticprerender, prerenderToNodeStream 도입
  • 데이터가 모두 준비될 때까지 대기 후 정적 HTML 생성 (스트리밍 SSR과는 별개)

😎 기타 개선

  • Suspense pre-warming, Hydration diff 로그 향상
  • useDeferredValue(value, initialValue)로 초기 표시값 제어