[React] react 19 버전 주요 변경점 최신 기술

useActionState, use, useOptimistic, metadata

Posted by lim.Chuck on January 9, 2025

[React]

  1. [React] vite React설치
  2. Project initial setting
  3. Hooks && Library
    1. [React] Javascript 파일을 Typescript Import해보자
    2. [React] react-router-dom 알아보고 사용하자
    3. [React] child 컴포넌트에서 함수호출하기
    4. [React] useCallback
    5. [React] framer-motion 알아보고 사용하자
    6. [React] Context API
    7. [React] react lazy 지연로딩 예제
    8. [React] react에서 캐싱처리를 위한 react queryd와 indexDB 비교분석
    9. [React] 스타일 라이브러리 styled-components stitches 비교
  4. [React] 프론트 에러추적 도구 Sentry 사용해보기
  5. [React] React 19 주요 변경점
  6. [React] DOM이란 무엇이고? 가상 DOM이란 무엇인가?
  7. [React] preact 알아보기

1. 🎭 useActionState(액션) 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import { useActionState } from "react";

function SpaceshipLaunchControl() {
  // 🎯 우주선 발사 컨트롤 센터
  const [error, launchAction, isLaunching] = useActionState(
    async (_, formData: FormData) => {
      const pilotName = formData.get("pilot") as string;

      // 🔍 안전 체크
      if (!pilotName.trim()) {
        return new Error("🚫 파일럿 이름이 필요합니다!");
      }

      // 🎲 랜덤 우주 이벤트 발생
      if (Math.random() > 0.7) {
        return new Error("⚠️ 소행성 벨트 발견! 발사 중단!");
      }

      // 🕐 발사 카운트다운
      await new Promise((resolve) => setTimeout(resolve, 1500));

      console.log("🌟 발사 성공! 파일럿:", pilotName);
    }
  );

  return (
    <div style={{ padding: "20px", maxWidth: "500px", margin: "0 auto" }}>
      <h1>🚀 우주선 발사 통제 센터</h1>

      <form action={launchAction}>
        <input
          name="pilot"
          type="text"
          placeholder="파일럿 이름을 입력하세요"
          style={{
            padding: "10px",
            marginRight: "10px",
            borderRadius: "4px",
            border: "1px solid #ccc",
          }}
        />
        <button
          disabled={isLaunching}
          style={{
            padding: "10px 20px",
            backgroundColor: isLaunching ? "#ccc" : "#007bff",
            color: "white",
            border: "none",
            borderRadius: "4px",
            cursor: isLaunching ? "not-allowed" : "pointer",
          }}
        >
          {isLaunching ? "🔄 발사 준비 중..." : "🚀 발사!"}
        </button>
      </form>

      {/* 🎨 상태 디스플레이 */}
      {error && (
        <p
          style={{
            color: "red",
            marginTop: "15px",
            padding: "10px",
            backgroundColor: "#ffebee",
            borderRadius: "4px",
          }}
        >
          {error.message}
        </p>
      )}

      {isLaunching && (
        <p
          style={{
            color: "#2196f3",
            marginTop: "15px",
            padding: "10px",
            backgroundColor: "#e3f2fd",
            borderRadius: "4px",
          }}
        >
          🎯 발사 시퀀스 진행 ...
        </p>
      )}
    </div>
  );
}

export default SpaceshipLaunchControl;

🎮 실제 사용 예시

  1. 간단한 로그인
1
2
3
4
5
const [error, loginAction, isLogging] = useActionState(async (_, formData) => {
  const username = formData.get("username") as string;
  if (!username) return new Error("사용자명을 입력하세요");
  // 로그인 로직...
});
  1. 파일 업로드
1
2
3
4
5
6
7
const [error, uploadAction, isUploading] = useActionState(
  async (_, formData) => {
    const file = formData.get("file") as File;
    if (!file) return new Error("파일을 선택하세요");
    // 업로드 로직...
  }
);

⚠️ 주의사항

  1. 에러 처리

    • throw 대신 return new Error() 사용
    • 에러 객체는 자동으로 error 상태로 전달됨
  2. 비동기 처리

    • async/await 사용 가능
    • Promise 처리 중에는 isPending이 true
  3. 폼 데이터

    • input의 name 속성 필수
    • FormData 객체로 자동 전달
  4. 타입 안전성

    • FormData.get()의 반환값은 타입 단언 필요

2. 🚀React 19 use Hook 완벽 가이드!

use Hook은 React 19에서 도입된 새로운 훅으로, Promise를 직접 컴포넌트에서 사용할 수 있게 해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { Suspense, use } from "react";

// 1️⃣ Promise를 생성하는 함수
function fetchSpaceData(type: "planets" | "astronauts" | "ships") {
  return new Promise<string[]>((resolve) => {
    setTimeout(() => {
      const data = {
        planets: ["수성", "금성", "지구", "화성", "목성"],
        astronauts: ["닐 암스트롱", "유리 가가린", "이소연"],
        ships: ["아폴로 11호", "스페이스X", "소유즈"],
      };
      resolve(data[type]);
    }, 1500);
  });
}

// 2️⃣ Promise 인스턴스 생성 (중요!)
const planetsPromise = fetchSpaceData("planets");
const astronautsPromise = fetchSpaceData("astronauts");
const shipsPromise = fetchSpaceData("ships");

// 3️⃣ 데이터를 표시할 컴포넌트
function SpaceInfo({
  promise,
  title,
}: {
  promise: Promise<string[]>;
  title: string;
}) {
  const data = use(promise);
  return (
    <div>
      <h3>{title}</h3>
      <ul>
        {data.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

// 4️⃣ 메인 컴포넌트
function App() {
  return (
    <div style={{ maxWidth: "600px", margin: "20px auto" }}>
      <h1>🌌 우주 데이터 뷰어</h1>

      <Suspense fallback={<div>🚀 행성 데이터 로딩 ...</div>}>
        <SpaceInfo promise={planetsPromise} title="행성 목록" />
      </Suspense>

      <Suspense fallback={<div>👨‍🚀 우주인 데이터 로딩 ...</div>}>
        <SpaceInfo promise={astronautsPromise} title="우주인 목록" />
      </Suspense>

      <Suspense fallback={<div>🛸 우주선 데이터 로딩 ...</div>}>
        <SpaceInfo promise={shipsPromise} title="우주선 목록" />
      </Suspense>
    </div>
  );
}

export default App;

🎮 실제 사용 예시

  1. 단순 데이터 로딩
1
2
3
4
function UserProfile({ id }: { id: number }) {
  const user = use(fetchUser(id));
  return <div>{user.name}</div>;
}
  1. 병렬 데이터 로딩
1
2
3
4
5
6
7
8
9
10
function Dashboard() {
  const users = use(fetchUsers());
  const posts = use(fetchPosts());
  return (
    <>
      <UserList users={users} />
      <PostList posts={posts} />
    </>
  );
}

⚠️ 주의사항

  1. Suspense 필수

    • use Hook을 사용하는 컴포넌트는 반드시 Suspense로 감싸야 함
    • 로딩 상태 처리를 위해 필요
  2. Promise 캐싱

    • 동일한 Promise는 캐시됨
    • 새로운 데이터가 필요하면 새 Promise 생성 필요
  3. 에러 처리

    • ErrorBoundary와 함께 사용 권장
    • Promise reject 시 자동으로 에러 처리
  4. 서버 컴포넌트 호환

    • 서버 컴포넌트에서도 사용 가능
    • 클라이언트/서버 모두에서 동작

🌟 장점

  1. 코드 간소화

    • useState + useEffect 조합 제거
    • 더 선언적인 데이터 로딩
  2. 성능 최적화

    • 자동 Suspense 통합
    • 효율적인 리소스 관리
  3. 더 나은 UX

    • 로딩 상태 자동 처리
    • 스무스한 전환 효과

3. 🎯useOptimistic이란?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import { useState, useOptimistic } from "react";
import "./App.css";

function App() {
  // 1️⃣ 실제 이름 상태
  const [name, setName] = useState("(아직 이름이 없어요!)");

  // 2️⃣ 낙관적 UI를 위한 상태
  const [optimisticName, setOptimisticName] = useOptimistic(name);

  // 3️⃣ 폼 제출 처리
  async function submitAction(formData: FormData) {
    const newName = formData.get("name") as string;

    if (!newName.trim()) {
      alert("이름을 입력해주세요! 🙏");
      return;
    }

    // 4️⃣ 낙관적 업데이트 (즉시 반영)
    setOptimisticName("" + newName);

    // 5️⃣ 서버 요청 시뮬레이션 (1초 대기)
    await new Promise((resolve) => setTimeout(resolve, 1000));

    // 6️⃣ 실제 상태 업데이트
    setName("" + newName);
  }

  return (
    <div>
      <h1> 이름 변경 마법사 </h1>

      <div>
        <h2>현재 이름:</h2>
        <p>{optimisticName}</p>
      </div>

      <form action={submitAction}>
        <div>
          <label>
            <span>새로운 이름:</span>
            <input
              name="name"
              type="text"
              placeholder="여기에 이름을 입력하세요"
            />
          </label>
          <button type="submit"> 변경하기</button>
        </div>
      </form>

      <div>
        <p>
          💡 <strong>작동 방식:</strong>
        </p>
        <ol>
          <li>이름을 입력하고 변경하기 버튼을 클릭하세요</li>
          <li>즉시  모래시계와 함께 임시 반영됩니다</li>
          <li>1   체크마크와 함께 최종 반영됩니다</li>
        </ol>
      </div>
    </div>
  );
}

export default App;

🎮 사용 방법

  1. 기본 구문
1
const [optimisticState, setOptimisticState] = useOptimistic(currentState);
  1. 업데이트 패턴
1
2
3
4
5
// 낙관적 업데이트
setOptimisticState(newValue);

// 실제 업데이트
setCurrentState(finalValue);

🎯 활용 사례

  1. 좋아요 버튼
1
2
setOptimisticLikes(likes + 1); // 즉시 증가
await updateLikes(); // 서버 반영
  1. 폼 제출
1
2
setOptimisticStatus("저장 중..."); // 즉시 표시
await saveData(); // 데이터 저장

⚠️ 주의사항

  1. 상태 정합성

    • 실제 상태와 동기화 필요
    • 에러 처리 고려
  2. 사용자 경험

    • 적절한 로딩 표시
    • 실패 시 피드백
  3. 성능 고려

    • 불필요한 업데이트 방지
    • 메모리 사용 최적화

🌟 장점

  1. 향상된 UX

    • 즉각적인 피드백
    • 자연스러운 전환
  2. 개발 편의성

    • 간단한 API
    • 직관적인 사용법
  3. 안정성

    • 자동 상태 관리
    • 에러 복구 기능

4. 📚 React 19의 메타데이터 & 리소스 관리 완벽 가이드!

1. Document 메타데이터 지원

React 19에서는 메타데이터 태그를 컴포넌트에서 직접 렌더링할 수 있는 기능이 추가되었습니다.

1️⃣ 기본 사용법

function BlogPost({ post }) {
  return (
    <article>
      {/* 메타데이터 태그들이 자동으로 <head>로 이동됩니다 */}
      <title>{post.title}</title>
      <meta name="description" content={post.description} />
      <meta name="keywords" content={post.keywords} />
      <link rel="canonical" href={`https://mysite.com/blog/${post.id}`} />

      {/* 실제 콘텐츠 */}
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

2. 스타일시트 관리

1️⃣ precedence를 통한 스타일시트 우선순위 관리

function Layout() {
  return (
    <>
      {/* 높은 우선순위 스타일 */}
      <link
        rel="stylesheet"
        href="/critical.css"
        precedence="high"
      />

      {/* 기본 우선순위 스타일 */}
      <link
        rel="stylesheet"
        href="/main.css"
        precedence="default"
      />

      <main>
        {/* 컨텐츠 */}
      </main>
    </>
  )
}

2️⃣ Suspense와 함께 사용

function DynamicContent() {
  return (
    <Suspense fallback={<Loading />}>
      <link
        rel="stylesheet"
        href="/dynamic-styles.css"
        precedence="default"
      />
      <DynamicComponent />
    </Suspense>
  )
}

3. 리소스 프리로딩

1️⃣ 다양한 프리로딩 API

import {
  prefetchDNS,
  preconnect,
  preload,
  preinit
} from 'react-dom'

function ResourceLoader() {
  // 스크립트 미리 로드 & 실행
  preinit('https://example.com/script.js', {
    as: 'script'
  })

  // 폰트 미리 로드
  preload('https://example.com/font.woff2', {
    as: 'font'
  })

  // DNS 미리 조회
  prefetchDNS('https://api.example.com')

  // 연결 미리 수립
  preconnect('https://api.example.com')

  return <div>컨텐츠...</div>
}

4. 주요 특징

  1. 메타데이터
  • 컴포넌트 내에서 직접 선언
  • 자동으로 <head>로 이동
  • SSR & CSR 모두 지원
  1. 스타일시트
  • 우선순위 기반 로딩
  • 자동 중복 제거
  • Suspense 통합
  1. 리소스 최적화
  • 다양한 프리로딩 옵션
  • 성능 최적화
  • 자동 우선순위 설정

⚠️ 주의사항

  1. 메타데이터

    • 복잡한 메타데이터는 라이브러리 사용 고려
    • SEO 요구사항 확인
  2. 스타일시트

    • precedence 값 신중히 설정
    • 로딩 순서 고려
  3. 리소스

    • 과도한 프리로딩 주의
    • 대역폭 고려

🌟 장점

  1. 개발 편의성

    • 선언적 메타데이터
    • 간단한 리소스 관리
  2. 성능 최적화

    • 자동 최적화
    • 효율적인 로딩
  3. 유지보수

    • 컴포넌트 기반 관리
    • 명확한 의존성

React 19 공식 블로그를 참고하여 최신 기능들을 적극 활용해보세요! 😊