React 18 Concurrent Features


React 18에서 도입된 Concurrent Rendering은 React가 여러 버전의 UI를 동시에 준비할 수 있게 해주는 핵심 기능입니다. 사용자 경험을 크게 향상시키는 새로운 훅과 API를 제공합니다.



언제 사용하나요?



  • 대규모 리스트 렌더링 시 입력 지연 방지

  • 페이지 전환 시 이전 페이지를 유지하며 로딩

  • 검색 자동완성 등 빈번한 상태 업데이트

  • 사용자 인터랙션 우선순위 관리



React 18 업그레이드


// React 18 설치
npm install react@18 react-dom@18

// index.js 변경
import { createRoot } from "react-dom/client";

const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);


useTransition - 우선순위 낮추기


import { useState, useTransition } from "react";

function SearchPage() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
// 긴급: 입력값 즉시 업데이트
setQuery(e.target.value);

// 낮은 우선순위: 검색 결과 업데이트
startTransition(() => {
setResults(searchItems(e.target.value));
});
};

return (
<div>
<input value={query} onChange={handleChange} />
{isPending ? (
<div>검색 중...</div>
) : (
<ResultList results={results} />
)}
</div>
);
}


useDeferredValue - 값 지연


import { useDeferredValue, useMemo } from "react";

function ProductList({ searchQuery }) {
// searchQuery의 지연된 버전
const deferredQuery = useDeferredValue(searchQuery);

// 실제 필터링은 지연된 값으로
const filteredProducts = useMemo(
() => products.filter(p =>
p.name.includes(deferredQuery)
),
[deferredQuery]
);

// 지연 중일 때 시각적 피드백
const isStale = searchQuery !== deferredQuery;

return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
{filteredProducts.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}


Suspense 향상


import { Suspense, lazy } from "react";

const Comments = lazy(() => import("./Comments"));
const Sidebar = lazy(() => import("./Sidebar"));

function App() {
return (
<div>
<Header />
<Suspense fallback={<Spinner />}>
<main>
<Content />
<Suspense fallback={<CommentSkeleton />}>
<Comments />
</Suspense>
</main>
<Suspense fallback={<SidebarSkeleton />}>
<Sidebar />
</Suspense>
</Suspense>
</div>
);
}


Automatic Batching


// React 18에서는 모든 업데이트가 자동 배칭됨
function handleClick() {
// 이전: 각각 리렌더링
// React 18: 한 번만 리렌더링
setCount(c => c + 1);
setFlag(f => !f);
}

// 비동기 함수 안에서도 배칭됨
async function handleSubmit() {
await saveToServer();
setCount(c => c + 1); // 한 번만
setFlag(f => !f); // 리렌더링
}

// 배칭 해제가 필요한 경우
import { flushSync } from "react-dom";

flushSync(() => {
setCount(c => c + 1);
}); // 즉시 리렌더링

flushSync(() => {
setFlag(f => !f);
}); // 다시 리렌더링


useId - SSR 안전한 ID 생성


import { useId } from "react";

function FormField({ label }) {
const id = useId();

return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} />
</div>
);
}

// 여러 ID가 필요한 경우
function ComplexForm() {
const id = useId();
return (
<>
<label htmlFor={id + "-name"}>이름</label>
<input id={id + "-name"} />
<label htmlFor={id + "-email"}>이메일</label>
<input id={id + "-email"} />
</>
);
}


주의사항



  • Strict Mode에서 컴포넌트가 두 번 렌더링됨

  • Concurrent 기능은 createRoot 필수

  • 클래스 컴포넌트에서는 일부 기능 제한

  • IE 지원 중단됨