Updated:

// React 19의 주요 새 기능 예제

// 1. 자동 배치 처리 개선
function AutoBatchingExample() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  const handleClick = async () => {
    // React 18 이전에는 이벤트 핸들러 내에서만 배치 처리
    // React 18부터는 Promise, setTimeout, 네이티브 이벤트 핸들러 등에서도 배치 처리
    
    // 이 두 상태 업데이트는 하나의 렌더링으로 배치 처리됨
    setCount(c => c + 1);
    setFlag(f => !f);
    
    // 비동기 코드 내에서도 배치 처리
    await fetchSomething();
    
    // React 18부터는 이 두 업데이트도 배치 처리됨
    setCount(c => c + 1);
    setFlag(f => !f);
    
    setTimeout(() => {
      // React 18부터는 이 두 업데이트도 배치 처리됨
      setCount(c => c + 1);
      setFlag(f => !f);
    }, 1000);
  };
  
  console.log('Render'); // 배치 처리로 인해 렌더링 횟수 감소
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Flag: {String(flag)}</p>
      <button onClick={handleClick}>Update State</button>
    </div>
  );
}

// 2. Suspense와 동시성 기능
import { Suspense, lazy } from 'react';

// 지연 로딩 컴포넌트
const LazyComponent = lazy(() => import('./HeavyComponent'));

function SuspenseExample() {
  return (
    <div>
      <h1>Main Content</h1>
      
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

// 3. useTransition Hook
import { useTransition, useState } from 'react';

function TransitionExample() {
  const [isPending, startTransition] = useTransition();
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);
  
  const handleChange = (e) => {
    const value = e.target.value;
    setInput(value);
    
    // 우선순위가 낮은 상태 업데이트를 트랜지션으로 표시
    startTransition(() => {
      // 큰 리스트 생성 (무거운 작업)
      const newList = [];
      for (let i = 0; i < 10000; i++) {
        newList.push(`${value} item ${i}`);
      }
      setList(newList);
    });
  };
  
  return (
    <div>
      <input value={input} onChange={handleChange} />
      
      {isPending ? (
        <p>Updating list...</p>
      ) : (
        <ul>
          {list.map((item, index) => (
            <li key={index}>{item}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

// 4. useDeferredValue Hook
import { useDeferredValue, useState } from 'react';

function DeferredValueExample() {
  const [input, setInput] = useState('');
  // 지연된 값 - 우선순위가 낮은 렌더링에 사용
  const deferredInput = useDeferredValue(input);
  
  const handleChange = (e) => {
    setInput(e.target.value);
  };
  
  // 입력값과 지연된 값이 다른지 확인
  const isStale = input !== deferredInput;
  
  // 무거운 리스트 렌더링 시뮬레이션
  const list = [];
  for (let i = 0; i < 10000; i++) {
    list.push(<li key={i}>{deferredInput} item {i}</li>);
  }
  
  return (
    <div>
      <input value={input} onChange={handleChange} />
      
      <div style=>
        <p>Rendering with: {deferredInput}</p>
        <ul>{list}</ul>
      </div>
    </div>
  );
}

// 5. 서버 컴포넌트
// 참고: 이 코드는 Next.js나 다른 프레임워크에서 실행해야 함

// server-component.jsx (서버에서만 실행)
export default async function ServerComponent() {
  // 서버에서만 실행되는 코드
  // 데이터베이스에 직접 접근하거나 파일 시스템 사용 가능
  const data = await fetchDataFromDatabase();
  
  return (
    <div>
      <h2>Server Component</h2>
      <p>This component runs only on the server</p>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

// client-component.jsx
'use client'; // 클라이언트 컴포넌트 표시

import { useState } from 'react';

export default function ClientComponent({ serverData }) {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <h2>Client Component</h2>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <div>
        <h3>Data from server:</h3>
        <pre>{JSON.stringify(serverData, null, 2)}</pre>
      </div>
    </div>
  );
}

// 6. useId Hook
import { useId } from 'react';

function AccessibleForm() {
  // 고유 ID 생성
  const id = useId();
  const nameId = `${id}-name`;
  const emailId = `${id}-email`;
  
  return (
    <form>
      <div>
        <label htmlFor={nameId}>Name:</label>
        <input id={nameId} type="text" />
      </div>
      <div>
        <label htmlFor={emailId}>Email:</label>
        <input id={emailId} type="email" />
      </div>
    </form>
  );
}

// 7. 새로운 Strict Mode 동작
function StrictModeExample() {
  const [count, setCount] = useState(0);
  
  // React 18의 Strict Mode에서는 개발 모드에서 컴포넌트를 
  // 마운트 → 언마운트 → 다시 마운트 시퀀스로 실행
  // 이는 효과 정리(cleanup)가 올바르게 구현되었는지 확인하는 데 도움
  
  useEffect(() => {
    console.log('Effect running');
    
    return () => {
      console.log('Effect cleanup');
    };
  }, []);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

// 8. 새로운 Root API
import { createRoot } from 'react-dom/client';

// 이전 방식
// ReactDOM.render(<App />, document.getElementById('root'));

// 새로운 방식
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

// 9. Suspense를 활용한 데이터 페칭
// 참고: 이 패턴은 React 18에서 도입되었으며 React 19에서 더 발전

// 데이터 페칭 함수
function fetchData() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ name: 'John Doe', age: 30 });
    }, 2000);
  });
}

// 리소스 생성 함수
function createResource(promise) {
  let status = 'pending';
  let result;
  let error;
  
  const suspender = promise.then(
    data => {
      status = 'success';
      result = data;
    },
    err => {
      status = 'error';
      error = err;
    }
  );
  
  return {
    read() {
      if (status === 'pending') {
        throw suspender;
      } else if (status === 'error') {
        throw error;
      } else {
        return result;
      }
    }
  };
}

// 리소스 생성
const userResource = createResource(fetchData());

// 데이터를 읽는 컴포넌트
function UserDetails() {
  // 데이터가 준비되지 않았으면 Suspense로 처리될 Promise를 throw
  const user = userResource.read();
  
  return (
    <div>
      <h2>User Details</h2>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
    </div>
  );
}

function SuspenseDataFetchingExample() {
  return (
    <div>
      <h1>User Profile</h1>
      <Suspense fallback={<div>Loading user data...</div>}>
        <UserDetails />
      </Suspense>
    </div>
  );
}

// 10. 동시 렌더링 모드
import { startTransition } from 'react';

function ConcurrentRenderingExample() {
  const [tab, setTab] = useState('home');
  
  const switchTab = (newTab) => {
    // 트랜지션으로 표시하여 UI 응답성 유지
    startTransition(() => {
      setTab(newTab);
    });
  };
  
  return (
    <div>
      <div className="tabs">
        <button 
          className={tab === 'home' ? 'active' : ''}
          onClick={() => switchTab('home')}
        >
          Home
        </button>
        <button 
          className={tab === 'profile' ? 'active' : ''}
          onClick={() => switchTab('profile')}
        >
          Profile
        </button>
        <button 
          className={tab === 'settings' ? 'active' : ''}
          onClick={() => switchTab('settings')}
        >
          Settings
        </button>
      </div>
      
      <div className="content">
        {tab === 'home' && <HomeTab />}
        {tab === 'profile' && <ProfileTab />}
        {tab === 'settings' && <SettingsTab />}
      </div>
    </div>
  );
}

// 11. useFormStatus와 useFormState (React 19 실험적 기능)
import { useFormStatus, useFormState } from 'react-dom';

// 서버 액션 (Next.js 등에서 사용)
async function submitForm(prevState, formData) {
  // 서버에 데이터 제출
  const name = formData.get('name');
  const email = formData.get('email');
  
  // 유효성 검사
  if (!name || !email) {
    return { success: false, error: 'Name and email are required' };
  }
  
  // API 호출 시뮬레이션
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  return { success: true, data: { name, email } };
}

// 제출 버튼 컴포넌트
function SubmitButton() {
  // 폼의 제출 상태 가져오기
  const { pending } = useFormStatus();
  
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Submitting...' : 'Submit'}
    </button>
  );
}

// 폼 컴포넌트
function FormExample() {
  // 폼 상태 관리
  const [state, formAction] = useFormState(submitForm, {
    success: false,
    error: null,
    data: null
  });
  
  return (
    <form action={formAction}>
      {state.error && <p className="error">{state.error}</p>}
      {state.success && <p className="success">Form submitted successfully!</p>}
      
      <div>
        <label htmlFor="name">Name:</label>
        <input id="name" name="name" type="text" />
      </div>
      
      <div>
        <label htmlFor="email">Email:</label>
        <input id="email" name="email" type="email" />
      </div>
      
      <SubmitButton />
    </form>
  );
}

// 12. 새로운 에러 경계 API (React 19 예상 기능)
function ErrorBoundary({ fallback, children }) {
  const [error, setError] = useState(null);
  
  if (error) {
    return typeof fallback === 'function' 
      ? fallback({ error, reset: () => setError(null) })
      : fallback;
  }
  
  return (
    <ErrorBoundaryContext.Provider value=>
      {children}
    </ErrorBoundaryContext.Provider>
  );
}

// 사용 예시
function ErrorBoundaryExample() {
  return (
    <ErrorBoundary
      fallback={({ error, reset }) => (
        <div className="error-container">
          <h2>Something went wrong!</h2>
          <p>{error.message}</p>
          <button onClick={reset}>Try again</button>
        </div>
      )}
    >
      <ComponentThatMightError />
    </ErrorBoundary>
  );
}

코멘트: React 18과 19는 동시성(Concurrency) 렌더링을 중심으로 한 중요한 업데이트를 도입했습니다. 주요 기능은 다음과 같습니다:

  1. 자동 배치 처리 개선: 모든 상태 업데이트를 하나의 렌더링으로 배치 처리하여 성능 향상

  2. 동시성 기능:
    • useTransition: 우선순위가 낮은 상태 업데이트를 표시하여 UI 응답성 유지
    • useDeferredValue: 값의 업데이트를 지연시켜 중요한 업데이트 먼저 처리
    • <Suspense>: 비동기 작업 중 대체 UI 표시
  3. 서버 컴포넌트: 서버에서만 실행되는 컴포넌트로 번들 크기 감소 및 데이터 접근 간소화

  4. 새로운 Hooks:
    • useId: 접근성을 위한 고유 ID 생성
    • useFormStatususeFormState: 폼 제출 상태 관리 (실험적)
  5. 새로운 Root API: createRoot를 통한 새로운 렌더링 방식

  6. Strict Mode 개선: 개발 모드에서 컴포넌트 이중 마운트로 부작용 감지

이러한 기능들은 대규모 애플리케이션의 성능과 사용자 경험을 크게 향상시키며, 특히 복잡한 UI와 데이터 처리에 유용합니다.

Categories:

Updated:

Leave a comment