몽땅뚝딱 개발자

[React.js] React19 주요 특징과 최적화 포인트 본문

Development/React.js · Next.js

[React.js] React19 주요 특징과 최적화 포인트

레오나르도 다빈츠 2025. 4. 30. 20:18

 

React19를 토이프로젝트에 도입하게되면서 공부한 주요 특징들!

서버/클라이언트의 경계가 흐려지며 Next.js와의 결합구조가 강화되었다.

 

 

1. Actions

비동기 상태 업데이트를 간편하게 관리하는 새로운 패턴이다.

직접 setState하지 않아도 되며 로딩도 관리할 필요가 없어졌다! 👏🏻👏🏻

 

기본 형태는 const [state, submitAction, isPending] = useActionState(actionFn, initialState)이다.

  • actionFn: (prevState: StateType, formData: FormData) => Promise<StateType>
  • initialState: state의 초기값

 

📄 기존 방식 (React 18 이전)

import React, { useState } from 'react';

export default function ChatForm() {
  const [message, setMessage] = useState('');  // 상태 관리
  const [isPending, setIsPending] = useState(false);  // 로딩 상태 관리

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setIsPending(true);  // 로딩 시작

    // 비동기 요청 (fetch API)
    await fetch('/api/send', { 
      method: 'POST', 
      body: JSON.stringify({ message }) 
    });

    setIsPending(false);  // 로딩 끝
    setMessage('');  // 입력 필드 초기화
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={message}
        onChange={(e) => setMessage(e.target.value)} 
        placeholder="메시지 입력"
      />
      <button type="submit" disabled={isPending}>
        {isPending ? '전송 중...' : '전송'}
      </button>
    </form>
  );
}

 

 

📄 useActionState를 사용한 방식 (React 19)

'use client';
import { useActionState } from 'react';

async function sendMessage(prevState: string, formData: FormData) {
  const message = formData.get('message') as string;
  await fetch('/api/send', { method: 'POST', body: JSON.stringify({ message }) });
  return '메시지 전송 완료!';
}

export default function ChatForm() {
  const [message, submitAction, isPending] = useActionState(sendMessage, '');

  return (
    <form action={submitAction}>
      <input name="message" />
      <button type="submit" disabled={isPending}>전송</button>
      <p>{message}</p>
    </form>
  );
}

 

 

 

 

2. 새로운 훅의 등장 (useActionState, useOptimistic, useFormStatus)

  • useActionState: form action의 상태를 관리한다.
  • useOptimistic: 실제 요청이 끝나기 전에 먼저 화면을 업데이트 한다.
  • useFormStatus: 폼 전송 중 여부에 대해 알려준다.

 

📄 useOptimistic

비동기보다 먼저 UI를 미리 변경한다.

'use client';
import { useOptimistic } from 'react';
import { useState } from 'react';

export default function LikeButton() {
  const [likes, setLikes] = useState(0);

  // optimisticLikes는 UI에 먼저 보여줄 값, apply 함수는 낙관적 업데이트용
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (currentLikes: number, delta: number) => currentLikes + delta,
  );

  const handleClick = async () => {
    // 1. UI 먼저 업데이트 한 뒤
    addOptimisticLike(1);

    // 2. 서버에 반영한다.
    const res = await fetch('/api/like', { method: 'POST' });
    const data = await res.json();
    setLikes(data.totalLikes); // 실제 값으로 갱신
  };

  return (
    <button onClick={handleClick}>
      ❤️ 좋아요 {optimisticLikes}
    </button>
  );
}

 

📄 useFormStatus

'use client';
import { useFormStatus } from 'react';

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>{pending ? '전송 중...' : '제출'}</button>;
}

 

 

 

 

3. use API

기존 React 방식에서는 렌더링 중에 await를 쓰지 못한다.

그래서 아래와 같이 처리를 했었다.

import { useEffect, useState } from 'react';

function Profile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch('/api/user')
      .then((res) => res.json())
      .then(setUser);
  }, []);

  if (!user) return <p>로딩 중...</p>;

  return <p>{user.name}님 환영합니다!</p>;
}

 

React 19에서는 컴포넌트 밖에서 Promise를 정의할 수 있고 use가 마치 await 처럼 동작한다.

아래처럼 간단하게 작성하여 사용할 수 있다.

'use client';
import { use } from 'react';

// 컴포넌트 밖에서 Promise 정의
const userPromise = fetch('/api/user').then((res) => res.json());

export default function Profile() {
  const user = use(userPromise); // use가 Promise를 기다린다.

  return <p>{user.name}님, 환영합니다!</p>;
}

// ⚠️ Suspense로 감싸져 있어야 한다.
import { Suspense } from 'react';
import Profile from './Profile';

export default function Page() {
  return (
    <Suspense fallback={<p>로딩 중...</p>}>
      <Profile />
    </Suspense>
  );
}

 

 

📄 API를 여러개 동시에 호출하는 경우

'use client';
import { use } from 'react';

const userPromise = fetch('/api/user').then((res) => res.json());
const settingsPromise = fetch('/api/settings').then((res) => res.json());

export default function Dashboard() {
  const user = use(userPromise);
  const settings = use(settingsPromise);

  return (
    <div>
      <h1>{user.name}님의 대시보드</h1>
      <p>현재 테마: {settings.theme}</p>
    </div>
  );
}

 

 

 

 

4. 서버 컴포넌트 (Server Components)

React 18 버전은 모든 컴포넌트가 기본적으로 클라이언트 컴포넌트이고, React 19에서는 서버 컴포넌트가 기본으로 바뀌었다.

서버에서 렌더링하고 브라우저로 최소 데이터만 보내는 컴포넌트이다.

// 서버 컴포넌트 (ex: src/app/profile/page.tsx)
import { getUser } from '@/lib/db';

export default async function ProfilePage() {
  const user = await getUser();  // 서버 DB 직접 호출 가능!

  return <div>안녕하세요, {user.name}님!</div>;
}

 

 

 

 

5. 스타일시트 및 스크립트 최적화 (Suspense 통합)

CSS나 JS 파일 로딩을 더 똑똑하게 관리할 수 있다.

이전에는 HTML이 먼저 로드되고 CSS는 나중에 불러와져서 깜빡거리는 현상이 있었다.

JS 코드, CSS, HTML이 모두 준비된 후 나타나기 때문에 깔끔한 UX를 제공한다.

'use client';
import { Suspense } from 'react';

function Comments() {
  // 느리게 로딩되는 컴포넌트
  return <div>댓글 목록</div>;
}

export default function Page() {
  return (
    <div>
      <h1>게시글 제목</h1>
      <Suspense fallback={<div>댓글 불러오는 중...</div>}>
        <Comments />
      </Suspense>
    </div>
  );
}

 

 

 

 

'Development > React.js · Next.js' 카테고리의 다른 글

MobX 사용하기  (0) 2025.02.23
[React] Provider, useContext  (0) 2024.08.03
[Next.js] 스토리북 도입 (미작성)  (0) 2023.12.29
[Next.js] 스타일링 도구  (0) 2023.12.20
[Next.js] 기본 개념  (0) 2023.12.20
Comments