React 18 Concurrent Rendering 적용 후기

배경

3월에 React 18이 정식 출시된 후 미뤄왔던 업그레이드를 진행했다. 특히 검색 페이지의 성능 문제가 있어서 Concurrent Features를 적용해보기로 했다.

기존 문제는 검색어 입력 시 매 타이핑마다 무거운 필터링 로직이 실행되면서 입력이 버벅이는 현상이었다.

useTransition 적용

import { useState, useTransition } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const [searchResults, setSearchResults] = useState([]);

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // 즉시 반영
    
    startTransition(() => {
      // 무거운 검색 로직은 낮은 우선순위로
      const results = expensiveFilterFunction(value);
      setSearchResults(results);
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultList items={searchResults} />
    </>
  );
}

입력 필드는 즉시 반응하고, 결과 목록 업데이트는 지연시켰다. 체감 성능이 확실히 개선됐다.

주의사항

  • createRoot로 마이그레이션 필요 (기존 ReactDOM.render는 legacy 모드)
  • Suspense 경계 설정이 중요함
  • 모든 상태 업데이트를 transition으로 감쌀 필요는 없음

결과

사용자 입력 반응성이 눈에 띄게 좋아졌다. 다만 팀원들에게 useTransition 사용 시점을 설명하는 게 생각보다 어려웠다. "느린 업데이트"의 기준이 애매해서 케이스별로 판단이 필요했다.

다음은 Suspense를 활용한 데이터 fetching 패턴을 실험해볼 예정이다.