React 18 useDeferredValue로 검색 입력 성능 개선

문제 상황

상품 검색 페이지에서 사용자가 입력할 때마다 5000개 이상의 상품 목록을 필터링해서 보여주는 기능을 구현했다. 입력할 때마다 화면이 버벅이는 현상이 발생했고, 특히 한글 입력 시 자음이 씹히는 문제가 심각했다.

기존에는 debounce를 사용했지만, 입력 후 300ms 대기 시간이 답답하다는 피드백이 있었다.

useDeferredValue 적용

React 18에서 추가된 useDeferredValue를 사용하면 긴급한 업데이트(입력)와 덜 긴급한 업데이트(목록 렌더링)를 분리할 수 있다.

import { useState, useDeferredValue } from 'react';

function ProductSearch() {
  const [searchQuery, setSearchQuery] = useState('');
  const deferredQuery = useDeferredValue(searchQuery);

  const filteredProducts = useMemo(() => {
    return products.filter(product => 
      product.name.toLowerCase().includes(deferredQuery.toLowerCase())
    );
  }, [deferredQuery]);

  return (
    <div>
      <input 
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        placeholder="상품 검색"
      />
      <ProductList products={filteredProducts} />
    </div>
  );
}

입력 필드는 searchQuery로 즉시 업데이트하고, 무거운 필터링 연산은 deferredQuery를 사용해 지연시켰다.

결과

  • 입력 반응성이 즉각적으로 개선됨
  • 한글 입력 시 자음 씹힘 현상 해결
  • debounce 대기 시간 없이 자연스러운 UX
  • Concurrent Rendering 덕분에 React가 알아서 우선순위 조정

추가 최적화

목록이 업데이트 중임을 표시하기 위해 isPending을 활용했다.

const deferredQuery = useDeferredValue(searchQuery);
const isPending = searchQuery !== deferredQuery;

return (
  <div style={{ opacity: isPending ? 0.5 : 1 }}>
    <ProductList products={filteredProducts} />
  </div>
);

React 18의 Concurrent Features를 실전에서 사용해보니 단순히 성능 최적화를 넘어 UX 개선에 직접적인 효과가 있었다. 기존 debounce 패턴을 대체할 만한 선택지로 충분히 고려할 만하다.