본문 바로가기
C.W.K.
Stream
Lesson 03 of 07 · published

React 19 액션: 비동기 변경과 UI 상태 연결하기

~11 min · frameworks, react, react-19, actions, server-components

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"액션은 비동기 함수를 마법으로 만드는 기능이 아니라, 변경 작업의 상태를 React 흐름에 연결하는 약속이야."

폼 액션의 입력과 결과

React 19의 폼 액션에는 함수를 연결할 수 있고, 함수는 보통 FormData를 받아 변경 작업을 수행해. 입력 필드 이름은 문자열이므로 formData.get('title')의 결과를 곧바로 string이라고 단정하지 말고 존재 여부와 타입을 검증해야 해.

액션 함수가 반환하는 상태를 useActionState와 연결하면 이전 상태, 액션, 대기 여부를 한 흐름에서 다룰 수 있어. 상태 타입을 판별 합집합으로 만들면 성공 메시지와 필드 오류를 안전하게 렌더링할 수 있지.

낙관적 UI와 실제 결과

useOptimistic은 서버 응답을 기다리는 동안 예상 결과를 먼저 보여 주는 데 도움을 줘. 하지만 실패했을 때 되돌릴 수 있어야 하고, 같은 항목에 여러 변경이 겹칠 때 식별자가 안정적이어야 해.

낙관적 값은 서버의 진실을 대체하지 않아. 응답이 오면 정식 데이터로 재조정하고, 실패나 권한 거부를 사용자에게 숨기지 마.

서버와 클라이언트 경계

서버 컴포넌트는 비동기 데이터를 읽고 JSX를 만들 수 있지만, 상태 훅과 브라우저 이벤트가 필요한 부분은 클라이언트 경계에 둬. 프레임워크의 'use client' 표시는 그 파일이 만드는 의존 그래프에 영향을 주므로 상위에 무심코 붙이면 브라우저 번들이 커질 수 있어.

서버에서 클라이언트로 넘기는 props는 직렬화 가능한 공개 데이터여야 해. TypeScript가 함수나 클래스 인스턴스의 전송 가능성을 모두 증명해 주지는 않으므로 프레임워크 빌드와 실제 렌더 경로로 확인해야 해.

Action에는 'use server' 지시문을 사용하고, useTransition, useOptimistic, useActionState 같은 훅을 조합해 전환 상태와 낙관적 갱신, 폼 상태를 다룰 수 있어.

액션의 타입은 입력 검증, 대기 상태, 성공과 실패를 하나의 변경 계약으로 묶어야 해. 서버 경계와 사용자 피드백까지 포함해야 완성된 흐름이야.

Code

React 19 — Server Component, Client Component, Action·tsx
// Server Component — async 함수, 'use client' 없음.
export default async function PostsPage() {
  const posts = await fetchPosts();
  return (
    <div>
      {posts.map((p) => <PostCard key={p.id} post={p} />)}
    </div>
  );
}

// Client Component — top 의 'use client', hook 쓸 수.
'use client';
import { useState } from 'react';

export function LikeButton({ postId }: { postId: number }) {
  const [liked, setLiked] = useState(false);
  return (
    <button onClick={() => setLiked(!liked)}>
      {liked ? '❤️' : '🤍'}
    </button>
  );
}

// Action — async 함수, FormData 처리 가능.
async function createPost(formData: FormData) {
  'use server';
  const title = formData.get('title') as string;
  await db.posts.create({ title });
}

export function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}

External links

Exercise

게시물 목록을 읽는 서버 컴포넌트, 새 게시물 대화상자를 여는 클라이언트 컴포넌트, 생성을 처리하는 액션의 경계를 스케치해. 입력과 반환 타입이 각 경계를 넘을 수 있는지 확인해.
Hint
서버 컴포넌트는 비동기 JSX를 만들고, 클라이언트 컴포넌트는 상태 훅을 사용해. 액션은 FormData를 검증한 뒤 직렬화 가능한 결과를 반환하게 해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.