React 19+ Internals와 함께 웹 애플리케이션 현대화

발행: (2026년 3월 9일 PM 09:34 GMT+9)
10 분 소요
원문: Dev.to

Source: Dev.to

1. 동시 렌더링 & @use (데이터 페칭 및 서스펜션)

우리는 모든 명령형 useEffect‑ 기반 페칭과 수동으로 관리하던 로딩 상태를 선언형 @use(Promise) 로 교체하고 있습니다.
이렇게 하면 데이터 오케스트레이션의 복잡성이 사용자 코드에서 직접 Fiber Reconciler 로 이동합니다.

@use 가 내부에서 작동하는 방식 (Suspense 메커니즘)

  1. Fiber Reconciler 가 @use 로 Promise 를 읽는 컴포넌트를 처리합니다.
  2. Promise 가 이미 해결됐는지 확인합니다.
  3. Promise 가 아직 대기 중이면, 해당 컴포넌트를 나타내는 Fiber 노드가 Suspended 로 표시되고 Reconciler 가 Promise 를 트리 위로 throw 합니다.
  4. 가장 가까운 <Suspense> 경계가 throw 된 Promise 를 잡아 fallback UI(예: 로딩 스켈레톤)를 렌더링합니다.
  5. Promise 가 해결되면 Reconciler 에게 알림이 전달되고 자동으로 Update 를 스케줄링하여 해결된 데이터를 사용해 컴포넌트를 다시 렌더링합니다.

확장된 실제 코드 예시

import { use, Suspense } from 'react';

// Unified Harbor API wrapper returning a stable Promise
// `@use` is integrated into our data‑fetching layer
import { fetchUserProfile } from './api';

export function UserProfile({ id }: { id: string }) {
  // 1️⃣ Declarative, suspended resource reading
  // Throws if pending; caught by the nearest Suspense boundary
  const user = use(fetchUserProfile(id));

  return (
    <>
      {/* Suspense fallback would be defined by a parent */}
      <h2>Welcome, {user.name}</h2>
      <p>Email: {user.email}</p>
      {/* Other profile components can also use `@use` concurrently */}
    </>
  );
}

// Parent implementation (standard Forge Stack pattern)
export default function App() {
  return (
    <Suspense fallback={<div>Loading…</div>}>
      <UserProfile id="123" />
    </Suspense>
  );
}

2. Actions & useActionState (네이티브 폼 가로채기 & 라이프사이클)

우리는 수동으로 isSubmitting을 추적하고 검증 오류를 전파하던 방식을 떠나고 있습니다.
React 19의 useActionState와 통합함으로써 Fiber Reconciler가 전체 폼 제출 라이프사이클을 네이티브하게 처리합니다.

useActionState가 내부적으로 작동하는 방식 (가로채기와 라이프사이클)

PhaseWhat happens
Pending폼의 네이티브 submit 이벤트가 가로채어집니다(기본 페이지 새로고침 방지). React가 FormData를 추출하고 내부 Action Fiber 업데이트를 디스패치하며 isPendingtrue로 설정합니다.
Success서버 액션 Promise가 해결되면 Reconciler가 성공 결과(또는 정리된 폼 상태)를 포함한 Action State Update를 커밋합니다.
ErrorPromise가 거부되면 Reconciler가 실패를 포착하고 오류 세부 정보를 담은 Action State Update를 커밋합니다.

Reconciler는 업데이트가 특정 폼 요소와 연결되어 있음을 알고 있기 때문에 관련된 Fiber 노드만 다시 렌더링됩니다.

확장된 실제 코드 예시

import { useActionState } from 'react';

// Server‑action wrapper (Harbor backend integration)
import { signUpServerAction, ActionState } from './actions';

export function BearSignUpForm() {
  // `useActionState` returns [state, dispatch, isPending]
  // `state` is automatically propagated for form resets or corrections
  const [state, dispatchAction, isPending] = useActionState(
    signUpServerAction,
    { error: null, success: false } // Initial state
  );

  return (
    <form action={dispatchAction}>
      {/* `useActionState` handles error & success propagation natively */}
      {state.error && <p className="error">{state.error}</p>}
      {state.success && <p className="success">Successfully registered!</p>}

      {/* 3️⃣ Automatic `isPending` tracking – no manual `useState` needed */}
      <button type="submit" disabled={isPending}>
        {isPending ? 'Forging Account…' : 'Sign Up'}
      </button>
    </form>
  );
}

3. 낙관적 업데이트 및 useOptimistic (Zero‑Lag UI 및 롤백 메커니즘)

우리는 useOptimistic를 구현하여 채팅, 좋아요, 장바구니 추가와 같은 중요한 상호작용에 대해 서버 왕복을 기다리지 않고 즉시 사용자 피드백을 제공합니다.

useOptimistic가 내부적으로 작동하는 방식 (예측된 커밋 vs 실제 커밋)

  1. 예측useOptimistic가 호출될 때(예: addOptimisticMessage('hello')), React는 높은 우선순위의 동시 lane에 Predicted Fiber 업데이트를 생성합니다. 이 업데이트는 즉시 커밋되어 임시 UI 상태를 표시합니다.
  2. 실제 작업 – 실제 비동기 서버 액션(Harbor API 호출)은 나중의 lane에 별도의 동시 업데이트로 디스패치됩니다. React는 이제 예측된 업데이트와 실제 결과를 경쟁시킵니다.
  3. 롤백 – 서버 액션이 실패하면 Reconciler는 예측 lane의 커밋을 버립니다. 예측된 업데이트가 최종 커밋 상태가 되지 않았기 때문에, 이를 버리면 Fiber 트리가 마지막으로 알려진 실제 상태로 자동 복원되어 낙관적 UI가 제로 레그로 제거되며 수동 리셋 로직이 필요하지 않습니다.

확장된 실제 코드 예시

import { useState, useOptimistic } from 'react';

// Native Harbor async message action (Server Action)
import { sendMessageAction } from './actions';

export function BearChatInput() {
  const [messages, setMessages] = useState([]);

  // `useOptimistic` returns a function that immediately updates UI
  const addOptimisticMessage = useOptimistic(
    (msg: string) => setMessages(prev => [...prev, msg]), // optimistic update
    async (msg: string) => {
      // Real server call – runs concurrently
      await sendMessageAction(msg);
    }
  );

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const form = e.target as HTMLFormElement;
    const input = form.elements.namedItem('message') as HTMLInputElement;
    const text = input.value.trim();
    if (!text) return;

    // Optimistically add the message, then fire the real action
    addOptimisticMessage(text);
    input.value = '';
  };

  return (
    <form onSubmit={handleSubmit}>
      <ul>
        {messages.map((msg, i) => (
          <li key={i}>- {msg}</li>
        ))}
      </ul>
      <button type="submit">Send</button>
    </form>
  );
}

이 예시는 UI가 낙관적 함수에 의해 즉시 업데이트되는 동시에 실제 서버 호출은 백그라운드에서 실행되는 방식을 보여줍니다. sendMessageAction이 오류를 발생시키면 React가 자동으로 낙관적 추가를 롤백합니다.

낙관적 UI 예시 (TypeScript)

import { useState } from "react";
import { useOptimistic } from "react";
import { sendMessageAction } from "./api";

export default function Chat() {
  const [messages, setMessages] = useState([]);

  // -------------------------------------------------
  // Temporary optimistic state shown before server confirmation
  // useOptimistic(state, updateFn)
  // -------------------------------------------------
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessageText: string) => [
      ...state,
      { text: newMessageText, pending: true, id: "temp-" + Date.now() },
    ]
  );

  async function handleSubmit(formData: FormData) {
    const text = formData.get("message") as string;

    // 4️⃣ URGENT: Show optimistic message instantly (Zero‑Lag UI)
    addOptimisticMessage(text);

    // 5️⃣ Async server call (Harbor backend integration)
    try {
      // (This promise is slow)
      const confirmedMessage = await sendMessageAction(text);

      // 6a️⃣ Update “real” state on success – optimistic value removed
      setMessages(prev => [...prev, confirmedMessage]);
    } catch {
      // 6b️⃣ Automatic rollback on failure – optimistic value discarded
      // (Optionally show a toast to inform the user)
    }
  }

  return (
    <form
      onSubmit={e => {
        e.preventDefault();
        handleSubmit(new FormData(e.target as HTMLFormElement));
      }}
    >
 >
      {/* BearMessageList renders `optimisticMessages` concurrently */}
      <ul>
        {optimisticMessages.map(msg => (
          <li key={msg.id}>
            {msg.text}
            {msg.pending && " (pending)"}
          </li>
        ))}
      </ul>
      <button type="submit">Send</button>
    </form>
  );
}

Overview

이 시각적 프레젠테이션은 모든 Forge Stack 기본 구성 요소 전반에 걸친 포괄적인 통합을 요약합니다. (보이지 않는) 라이트‑모드 인포그래픽은 상호 연결된 경로에 라벨을 붙이며, 여기에는 다음이 포함됩니다:

  • startTransition – 긴급 lane vs. 비긴급 lane
  • @useSuspenseRehydration
  • useActionState 라이프사이클
  • useOptimistic – 예측된 상태 vs. 커밋된 상태

이 요소들은 애플리케이션 전반에 걸쳐 데이터 흐름을 동기화합니다.

다이어그램이 보여주는 내용

  • React Scheduler & Fiber Reconciler: 복잡한 시나리오를 관리하기 위해 내부에서 어떻게 작동하는지.
  • Non‑blocking Perceived Performance: 낙관적 업데이트, 전환, 서스펜스 경계 등을 효율적으로 처리하여 UI가 반응성을 유지하도록 보장.

(실제 다이어그램은 원본 문서에 포함되어 있으며, 위 원시들 간의 데이터와 제어 흐름을 보여줍니다.)

0 조회
Back to Blog

관련 글

더 보기 »