ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [FE 최적화 기술] Throttle, Debounce, useDeferredValue, useTransition 쉽게 정리하기 😊
    웹 개발 2025. 7. 21. 00:03

     

    리액트를 쓰다 보면 ‘입력이 느려요’, ‘렌더링이 버벅여요’ 같은 이야기를 들을 수 있다.

    (혹은 무한 랜더링...)

     

    그럴 때 우리가 쓸 수 있는 대표적인 성능 최적화 기법 네 가지가 바로 다음과 같다.

    위의 두 가지는 일반적인 방법론이고, 뒤의 두 가지 훅은 리액트 18부터 사용할 수 있는 최적화 훅이다.

    • 🧂 Throttling
    • 💧 Debouncing
    • 🕓 useDeferredValue
    • 🌀 useTransition

    이 글에서는 각 개념이 언제, 왜, 어떻게 쓰이는지를 쉬운 예제와 함께 알아보자.

     

    [🧂 Throttling] 너무 자주 부르지 않게 하기

    특정 시간 동안 이벤트가 여러 번 발생하더라도, 일정 시간 간격으로만 함수를 실행하는 방식이다.

     

    언제 사용할까?

    • 스크롤, resize 이벤트 같이 계속 발생하는 이벤트를 제한하고 싶을 때

    예제 (바닐라 JS + Lodash)

    import throttle from 'lodash.throttle';
    
    window.addEventListener('scroll', throttle(() => {
      console.log('스크롤 감지!');
    }, 1000));

     

    예제 (React)

    const handleScroll = useCallback(throttle(() => {
      console.log('스크롤');
    }, 1000), []);
    
    useEffect(() => {
      window.addEventListener('scroll', handleScroll);
      return () => window.removeEventListener('scroll', handleScroll);
    }, []);

     

     

    [💧 Debouncing]  입력이 끝나면 알려주기

    입력이 끝났다고 판단되는 순간에만 실행하는 방식입니다. 입력 도중엔 실행 안 함

     

    언제 사용 할까?

    • 검색창 입력, 자동완성처럼 사용자가 입력을 끝낼 때만 API 요청을 보내고 싶을 때

    예제 (바닐라 JS + Lodash)

    import debounce from 'lodash.debounce';
    
    const search = debounce((value: string) => {
      console.log('검색어:', value);
    }, 500);
    
    <input onChange={(e) => search(e.target.value)} />

    예제 (React)

    const [query, setQuery] = useState('');
    const debouncedQuery = useMemo(() => debounce(setQuery, 500), []);
    
    return <input onChange={(e) => debouncedQuery(e.target.value)} />;

     

     

    그리고 이제 리액트 훅으로 제공되는 최적화 기능을 살펴보자.

    [🕓 useDeferredValue] 급한 것부터 그리고, 나머진 천천히 진행하기

    값 업데이트를 좀 더 나중에 처리해서, 급한 UI는 먼저 렌더링할 수 있게 합니다.

     

    언제 사용할까?

    • 입력은 즉시 반영되지만, 연산 비용이 큰 컴포넌트 렌더링은 늦추고 싶을 때

    예제

    import { useState, useDeferredValue } from 'react';
    
    function SearchResults({ query }: { query: string }) {
      const deferredQuery = useDeferredValue(query);
      const results = expensiveSearch(deferredQuery);
    
      return <ul>{results.map(r => <li key={r}>{r}</li>)}</ul>;
    }

    query가 바뀌면 즉시 UI는 반응하지만, 검색 결과는 조금 천천히 렌더링된다. 성능 문제 있을 때 사용할 수 있다.

     

    [🌀 useTransition] 느린 작업은, 배경에서

    React에 “이건 급하지 않아요~”라고 알려주는 훅. 낮은 우선순위의 작업을 나중에 처리하게 한다.

    언제 사용할까?

    • 입력은 빠르게, 무거운 리스트 렌더링은 느리게 하고 싶을 때
    • 버튼 클릭 → 화면 전환 시, 로딩을 자연스럽게 하고 싶을 때

    예제

    import { useState, useTransition } from 'react';
    
    function Search() {
      const [query, setQuery] = useState('');
      const [list, setList] = useState<string[]>([]);
      const [isPending, startTransition] = useTransition();
    
      const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const value = e.target.value;
        setQuery(value);
        startTransition(() => {
          const result = expensiveSearch(value);
          setList(result);
        });
      };
    
      return (
        <>
          <input value={query} onChange={handleChange} />
          {isPending && <p>로딩 중...</p>}
          <ul>{list.map(item => <li key={item}>{item}</li>)}</ul>
        </>
      );
    }

    startTransition 안의 코드는 "급하지 않은 작업"으로 처리된다. 입력은 빠르게 반응하고, 무거운 리스트 렌더링은 부드럽게!

     

    startTransition은 꼭 비동기 작업이 아니더라도 오래 걸리는 상태 업데이트 자체에 적용된다.

    startTransition 안에서는 API 호출 같은 비동기 작업이 아닌, 무거운 상태 업데이트도 포함될 수 있다.
    예: 리스트 필터링, 가상 DOM diff 등.

    ✨ 언제 뭘 써야 할까?

    스크롤/리사이즈 이벤트 너무 자주 호출됨 🧂 Throttling
    검색창에서 입력 끝날 때만 처리하고 싶음 💧 Debouncing
    무거운 컴포넌트를 늦게 렌더링하고 싶음 🕓 useDeferredValue
    빠른 입력 + 느린 리스트 렌더링 분리 🌀 useTransition

     

    React에서 UX를 최적화하는 건 속도를 높이는 게 아니라 느리게 보여야 할 걸 잘 늦추는 것에 가깝다.
    Throttle, Debounce는 JS 기반의 전통적인 성능 최적화,
    useDeferredValue, useTransition은 React의 렌더링 스케줄링을 조절하는 최신 기법이다.

    필요에 따라서 적절한 기법을 사용하여 최적화할 수 있다.

     

    📚 참고자료

    🔵 React 공식 문서

    🟣 MDN Web Docs (브라우저 기반 개념)

    🟠 라이브러리 문서

    🟢 블로그 글

Designed by Tistory.