React 요약 15 - React 19의 새로운 기능
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) 렌더링을 중심으로 한 중요한 업데이트를 도입했습니다. 주요 기능은 다음과 같습니다:
자동 배치 처리 개선: 모든 상태 업데이트를 하나의 렌더링으로 배치 처리하여 성능 향상
- 동시성 기능:
useTransition
: 우선순위가 낮은 상태 업데이트를 표시하여 UI 응답성 유지
useDeferredValue
: 값의 업데이트를 지연시켜 중요한 업데이트 먼저 처리<Suspense>
: 비동기 작업 중 대체 UI 표시서버 컴포넌트: 서버에서만 실행되는 컴포넌트로 번들 크기 감소 및 데이터 접근 간소화
- 새로운 Hooks:
useId
: 접근성을 위한 고유 ID 생성
useFormStatus
와useFormState
: 폼 제출 상태 관리 (실험적)새로운 Root API:
createRoot
를 통한 새로운 렌더링 방식- Strict Mode 개선: 개발 모드에서 컴포넌트 이중 마운트로 부작용 감지
이러한 기능들은 대규모 애플리케이션의 성능과 사용자 경험을 크게 향상시키며, 특히 복잡한 UI와 데이터 처리에 유용합니다.
Leave a comment