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

문제 상황

사용자가 검색어를 입력할 때마다 5000개 이상의 아이템을 필터링하는 화면에서 입력 지연이 발생했다. 타이핑할 때마다 리렌더링이 발생하면서 UX가 좋지 않았다.

기존에는 lodash의 debounce를 사용했는데, 이 경우 입력 후 일정 시간을 기다려야 해서 즉각적인 피드백이 어려웠다.

useDeferredValue 적용

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

import { useState, useDeferredValue, useMemo } from 'react';

function SearchList({ items }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const filteredItems = useMemo(() => {
    return items.filter(item => 
      item.name.toLowerCase().includes(deferredQuery.toLowerCase())
    );
  }, [items, deferredQuery]);

  return (
    <>
      <input 
        value={query} 
        onChange={(e) => setQuery(e.target.value)}
        placeholder="검색..."
      />
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

결과

입력 필드는 즉시 반응하고, 목록 필터링은 낮은 우선순위로 처리되어 타이핑 끊김 현상이 사라졌다. debounce와 달리 별도의 타이머 관리가 필요 없고, React의 동시성 기능을 활용해 자연스러운 UX를 제공할 수 있었다.

Concurrent 렌더링이 활성화된 환경(React 18 + createRoot)에서만 제대로 동작하므로 마이그레이션이 선행되어야 한다.