React 18 Concurrent Rendering과 useDeferredValue 적용 후기

문제 상황

상품 목록 페이지에서 검색어 입력 시 약 2000개의 아이템을 필터링하는 기능을 구현했다. 타이핑할 때마다 입력 필드가 버벅이면서 사용자 경험이 매우 나빴다.

function ProductList() {
  const [searchText, setSearchText] = useState('');
  const filteredProducts = products.filter(p => 
    p.name.includes(searchText)
  );
  
  return (
    <>
      <input value={searchText} onChange={e => setSearchText(e.target.value)} />
      <ProductGrid products={filteredProducts} />
    </>
  );
}

입력할 때마다 2000개 아이템을 전부 렌더링하다 보니 메인 스레드가 블로킹되었다.

useDeferredValue 적용

React 18에서 제공하는 useDeferredValue를 사용해 검색 결과 렌더링의 우선순위를 낮췄다.

import { useState, useDeferredValue } from 'react';

function ProductList() {
  const [searchText, setSearchText] = useState('');
  const deferredSearchText = useDeferredValue(searchText);
  
  const filteredProducts = products.filter(p => 
    p.name.includes(deferredSearchText)
  );
  
  return (
    <>
      <input value={searchText} onChange={e => setSearchText(e.target.value)} />
      <ProductGrid products={filteredProducts} />
    </>
  );
}

input의 상태는 즉시 업데이트되고, 필터링 결과는 지연되어 렌더링된다. 사용자는 타이핑이 막히지 않는다고 느낀다.

결과

  • 입력 지연 체감 거의 없음
  • debounce보다 자연스러운 UX
  • Concurrent Rendering 덕분에 React가 알아서 우선순위 조정

기존에 사용하던 lodash debounce는 제거했다. React 18의 동시성 기능이 생각보다 실용적이었다.