매일 업데이트
2023-09-30 21:15 7 min

디바운싱을 사용하여 React에서 검색 성능을 향상시키는 방법

리액트(React)에서 검색 기능을 구현할 때, 사용자가 입력 필드에 글자를 입력할 때마다 onChange 핸들러가 검색 기능을 작동시킵니다. 이러한 접근 방식은 특히 API 호출이나 데이터베이스 쿼리와 같이 자원 소모가 큰 작업을 수행할 때 성능 저하를 유발할 수 있습니다. 잦은 검색 기능 호출은 웹 서버에 과부하를 초래하여 서버 충돌이나 사용자 인터페이스의 응답 지연을 야기할 수 있습니다. 디바운싱은 이러한 문제점을 해결하는 데 효과적인 기술입니다.

디바운싱이란 무엇인가?

일반적으로 리액트에서는 사용자가 키를 누를 때마다 onChange 핸들러 함수를 호출하여 검색 기능을 구현합니다. 아래 코드는 이러한 작동 방식을 보여줍니다.

import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("검색어:", searchTerm);
  };

  const handleChange = (e) => {
    setSearchTerm(e.target.value);

    handleSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="검색어를 입력하세요..."
    />
  );
}

위 코드는 기능적으로 작동하지만, 키를 누를 때마다 검색 결과를 업데이트하기 위해 백엔드 서버를 호출하는 것은 부담이 클 수 있습니다. 예를 들어, 사용자가 "webdev"를 검색하는 경우, 애플리케이션은 "w", "we", "web" 등의 검색어를 사용하여 서버에 요청을 여러 번 보내게 됩니다.

디바운싱은 특정 시간 지연이 경과할 때까지 함수 실행을 미루는 기술입니다. 디바운스 함수는 사용자의 입력을 감지하고, 지정된 지연 시간이 경과할 때까지 검색 핸들러 호출을 막습니다. 만약 사용자가 지연 시간 내에 계속해서 입력하는 경우, 타이머는 재설정되고 리액트는 새로운 지연 시간을 적용하여 함수를 다시 호출합니다. 이 과정은 사용자가 입력을 멈출 때까지 계속됩니다.

디바운싱을 사용하면, 사용자가 입력을 멈출 때까지 기다린 후 검색 요청을 보내기 때문에, 불필요한 요청을 줄여 서버 부하를 감소시키고, 애플리케이션이 필요한 검색 요청만 처리하도록 할 수 있습니다.

리액트에서 검색 기능을 디바운싱하는 방법

디바운스를 구현하는 데에는 다양한 라이브러리가 사용될 수 있습니다. JavaScript의 setTimeout 및 clearTimeout 함수를 사용하여 직접 구현하는 방법도 있습니다.

여기서는 lodash 라이브러리의 디바운스 기능을 활용하여 구현하는 방법을 살펴보겠습니다.

리액트 프로젝트가 이미 설정되어 있다고 가정하고, Search라는 이름의 새 컴포넌트를 생성합니다. 만약 아직 프로젝트가 없다면 create-react-app 유틸리티를 사용하여 새로운 리액트 앱을 만들 수 있습니다.

먼저, 검색 컴포넌트 파일에 아래 코드를 복사하여 키 입력 시마다 처리기 함수를 호출하는 기본적인 검색 입력 상자를 만듭니다.

import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("검색어:", searchTerm);
  };

  const handleChange = (e) => {
    setSearchTerm(e.target.value);

    handleSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="검색어를 입력하세요..."
    />
  );
}

이제 handleSearch 함수를 디바운스하기 위해, lodash의 디바운스 함수에 전달합니다.

import debounce from "lodash.debounce";
import { useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = () => {
    console.log("검색어:", searchTerm);
  };
  const debouncedSearch = debounce(handleSearch, 1000);

  const handleChange = (e) => {
    setSearchTerm(e.target.value);

    debouncedSearch();
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="검색어를 입력하세요..."
    />
  );
}

디바운스 함수는 지연시킬 함수(handleSearch)와 지연 시간(밀리초 단위)을 인수로 받습니다.

위 코드는 사용자가 입력을 멈출 때까지 handleSearch 요청 호출을 지연시켜야 하지만, 리액트에서는 예상대로 작동하지 않습니다. 다음 섹션에서 그 이유를 설명합니다.

디바운싱과 재렌더링

현재 애플리케이션은 제어된 입력 방식을 사용합니다. 이는 상태 값이 입력 값 자체를 제어한다는 것을 의미합니다. 사용자가 검색 필드에 입력할 때마다, 리액트는 상태 값을 업데이트합니다.

리액트에서 상태 값이 변경되면, 해당 컴포넌트는 재렌더링되고, 컴포넌트 내의 모든 함수가 다시 실행됩니다.

앞서 제시된 검색 컴포넌트에서, 컴포넌트가 재렌더링될 때마다 리액트는 디바운스 함수를 다시 실행합니다. 이 함수는 지연 시간을 추적하는 새로운 타이머를 만들고 이전 타이머는 메모리에서 사라집니다. 시간이 지나면 검색 기능이 실행됩니다. 이는 검색 기능이 디바운싱되지 않고, 단순히 500ms의 지연 시간만 갖는다는 것을 의미합니다. 이 주기는 렌더링될 때마다 반복됩니다. 함수는 매번 새로운 타이머를 생성하고 이전 타이머는 만료된 다음, 검색 함수를 호출합니다.

디바운스 함수가 제대로 작동하려면, 한 번만 호출되어야 합니다. 이를 위해 컴포넌트 외부에서 디바운스 함수를 정의하거나 메모이제이션 기술을 사용할 수 있습니다. 이러한 방법을 통해 컴포넌트가 재렌더링되더라도 리액트는 디바운스 함수를 다시 실행하지 않도록 할 수 있습니다.

검색 컴포넌트 외부에서 디바운스 함수 정의하기

디바운스 함수를 다음과 같이 검색 컴포넌트 외부로 이동시킵니다.

import debounce from "lodash.debounce"

const handleSearch = (searchTerm) => {
  console.log("검색어:", searchTerm);
};

const debouncedSearch = debounce(handleSearch, 500);

이제 검색 컴포넌트에서 debouncedSearch 함수를 호출하고 검색어를 인수로 전달합니다.

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleChange = (e) => {
    setSearchTerm(e.target.value);

    debouncedSearch(searchTerm);
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="검색어를 입력하세요..."
    />
  );
}

이제 검색 기능은 지연 시간 경과 후, 사용자가 입력을 멈춘 후에만 실행됩니다.

디바운스 함수 메모이제이션

메모이제이션은 함수의 결과를 캐시하고, 동일한 인수를 사용하여 함수를 호출할 때 캐시된 결과를 재사용하는 기술입니다.

디바운스 함수를 메모이제이션하려면 useMemo 훅을 사용해야 합니다.

import debounce from "lodash.debounce";
import { useCallback, useMemo, useState } from "react";

export default function Search() {
  const [searchTerm, setSearchTerm] = useState("");

  const handleSearch = useCallback((searchTerm) => {
    console.log("검색어:", searchTerm);
  }, []);

  const debouncedSearch = useMemo(() => {
    return debounce(handleSearch, 500);
  }, [handleSearch]);

  const handleChange = (e) => {
    setSearchTerm(e.target.value);

    debouncedSearch(searchTerm);
  };

  return (
    <input
      onChange={handleChange}
      value={searchTerm}
      placeholder="검색어를 입력하세요..."
    />
  );
}

추가적으로, React가 한 번만 호출하도록 useCallback 훅으로 핸들 검색 함수를 감쌌습니다. useCallback 훅이 없다면, React는 useMemo 훅의 의존성이 변경될 때마다 다시 렌더링 시 핸들 검색 함수를 실행하고, 그 결과로 디바운스 함수를 호출하게 됩니다.

이제 리액트는 handleSearch 함수나 지연 시간이 변경될 때만 디바운스 함수를 호출합니다.

디바운스를 이용한 검색 최적화

때로는 속도를 늦추는 것이 성능 향상에 더 효과적일 수 있습니다. 특히 자원 소모가 큰 데이터베이스나 API 호출을 통한 검색 작업에서는 디바운스 기능을 사용하는 것이 바람직합니다. 이 기능을 사용하면, 백엔드 요청을 보내기 전에 의도적으로 지연 시간을 적용할 수 있습니다.

지연 시간이 경과하고 사용자가 입력을 멈춘 후에만 요청을 전송하므로, 서버에 대한 요청 수를 줄이는 데 도움이 됩니다. 이는 과도한 요청으로 인해 서버가 과부하되지 않도록 보호하고, 전반적인 성능을 효율적으로 유지하는 데 기여합니다.

저자
Korea

기술 트렌드와 실용적인 팁을 전하는 लेखक입니다.