"매 render를 interruptible 하게". React 18+ 의 핵심 — render 작업을 chunk 로 쪼개서 user input 처리 우선. useTransition, Suspense, useDeferredValue 가 API. React 19 에서 Server Components / Actions 와 결합되어 SSR/streaming 의 표준이 됨.
매 핵심
매 무엇이 concurrent 인가
기존 (legacy): render 시작하면 매 끝까지 sync — 그 사이 user input X.
Concurrent: render 를 작은 chunk 로 — frame budget (5ms) 다 쓰면 yield → user input 처리 → 재개.
결과: typing/scroll 중에도 heavy render 가 막지 않음.
매 핵심 API
useTransition: state update 를 "transition" 으로 mark — interruptible, lower priority.
useDeferredValue: value 의 stale version 을 사용 — debounce 와 비슷하지만 schedule-aware.
Suspense: async boundary — use(promise) / lazy() 와 결합.
startTransition: hook 외부에서 transition 사용.
<Activity> (React 19+): hidden sub-tree 의 mount 유지 + render 안 함.
매 응용
Filter / search (타이핑 안 끊김).
Tab switching (heavy tab content async load).
Route navigation (이전 page 보여주며 next page prepare).
Streaming SSR (renderToReadableStream).
💻 패턴
useTransition for filter
functionProductList({allProducts}:{allProducts: Product[]}){const[query,setQuery]=useState('');const[filtered,setFiltered]=useState(allProducts);const[isPending,startTransition]=useTransition();functionhandleChange(e: ChangeEvent<HTMLInputElement>){setQuery(e.target.value);// 매 high priority — input 즉시 반영
startTransition(()=>{setFiltered(allProducts.filter(p=>p.name.includes(e.target.value)));});}return(<><inputvalue={query}onChange={handleChange}/><divstyle={{opacity: isPending?0.5 : 1}}>{filtered.map(p=><Rowkey={p.id}product={p}/>)}</div></>);}
useDeferredValue for chart
functionDashboard({filter}:{filter: string}){constdeferred=useDeferredValue(filter);// 매 chart re-render 는 typing 후에 처리됨.
return<ExpensiveChartfilter={deferred}/>;}
Suspense + use() (React 19)
functionUser({userPromise}:{userPromise: Promise<User>}){constuser=use(userPromise);// 매 promise 대기 → Suspense 가 fallback
return<h1>{user.name}</h1>;}<Suspensefallback={<Skeleton/>}><UseruserPromise={fetchUser(id)}/></Suspense>
Streaming SSR (Next.js / Remix)
// app/page.tsx
exportdefaultasyncfunctionPage() {return(<><Header/>{/* 매 즉시 stream */}<Suspensefallback={<Skeleton/>}><SlowFeed/>{/* 매 준비 되는대로 stream */}</Suspense></>);}
<Activitymode={activeTab==='feed'?'visible':'hidden'}><Feed/>{/* 매 hidden 시 unmount X — state 보존, render 비용 0 */}</Activity>
Avoiding tearing — useSyncExternalStore
constvalue=useSyncExternalStore(store.subscribe,store.getSnapshot,store.getServerSnapshot);// 매 concurrent rendering 중 store value 일관성 보장.
Optimistic update with useOptimistic (React 19)
functionLikes({post}:{post: Post}){const[optimisticLikes,addLike]=useOptimistic(post.likes,(s,_)=>s+1);asyncfunctionhandleLike() {addLike(null);// 매 즉시 UI 반영
awaitfetch(`/like/${post.id}`,{method:'POST'});}return<buttononClick={handleLike}>{optimisticLikes}likes</button>;}