React

[React] 리액트 debounce로 검색 성능 최적화하기

solfa 2024. 5. 22. 22:50

umc 6주차 과제중 Debounce와 Throttling이라는 개념을 배웠다!

 

Debounce : 연속된 이벤트 호출을 제한하여 마지막 이벤트만 처리하도록 하는 기술

→ 연속된 이벤트가 발생할 때마다 타이머를 리셋하고, 일정 시간이 지난 후에 이벤트를 처리

  • Debounce는 주로 어디에 사용하나요?ex) 검색어 자동 완성 기능에서 사용자가 입력을 멈춘 후에만 서버에 요청을 보냄
  • 사용자 입력 필드에서 입력이 끝난 후 일정 시간 동안 입력이 없을 때만 서버에 요청을 보내는 경우

적용 과정

1. debounce hook 만들기

2. 적용하기

 

1. useDebounce.js hook 구현

import { useState, useEffect } from "react";

export const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
};

이 훅은 입력된 값이 일정 시간 동안 변경되지 않을 때까지 기다렸다가 업데이트된다.

사용자가 입력할 때마다 API 호출을 하지 않도록 도와주는 기능을 한다. 이 기능이 바로 debounce!

⭐️시간 차를 두기⭐️

 

  • value: 디바운스 처리할 값, 검색어 넣으면 됨
  • delay: 디바운스 대기 시간(밀리초)
  • useEffect: value가 변경될 때마다 타이머를 설정한다. 설정된 시간이 지나면 debouncedValue를 업데이트 한다. 타이머가 설정되는 동안 value가 다시 변경되면 이전 타이머는 취소된다.

 

 

2. 기존 search 페이지에 추가

 

기존 search 페이지 코드

import { useCallback, useEffect, useState } from "react";
import {
  StyleSearch,
  StyleTitle,
  StyledInput,
  StyledMovieCard,
  StyledMovieList,
  StyledSearchView,
  Poster,
  Overview,
  Title,
  Star,
} from "./Search.style";
import axios from "axios";

export const Search = () => {
  const [searchTerm, setSearchTerm] = useState("");
  const [movies, setMovies] = useState([]);
  const apiKey = "d2cb276ab0ca7b65595d1e9a2fd4ea84";

  const handleSearch = useCallback(async () => {
    if (searchTerm) {
      try {
        const response = await axios.get(
          `https://api.themoviedb.org/3/search/movie`,
          {
            params: {
              api_key: apiKey,
              query: searchTerm,
            },
          }
        );
        setMovies(response.data.results);
      } catch (error) {
        console.error("Error fetching movies:", error);
      }
    }
  }, [searchTerm]);

  useEffect(() => {
    handleSearch();
  }, [handleSearch]);

  const calculateStars = (vote_average) => {
    const stars = Math.round(vote_average);
    return stars > 0 ? Array(stars).fill("⭐").join("") : "";
  };

  return (
    <StyleSearch>
      <StyleTitle>🎥 Find your movies!</StyleTitle>
      <StyledInput
        type="text"
        placeholder="Type to search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <StyledSearchView>
        <StyledMovieList>
          {searchTerm &&
            movies.map((movie) => (
              <StyledMovieCard key={movie.id}>
                <Poster
                  src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
                  alt={`Movie Poster of ${movie.title}`}
                />
                <Title>{movie.title}</Title>
                <Star>{`Rating: ${calculateStars(movie.vote_average)}`}</Star>
                <Overview>
                  <p>{movie.overview || "No overview available."}</p>
                </Overview>
              </StyledMovieCard>
            ))}
        </StyledMovieList>
      </StyledSearchView>
    </StyleSearch>
  );
};

 

 

useDebounce 훅을 추가한 search 페이지 코드

import { useCallback, useEffect, useState } from "react";
import {
  StyleSearch,
  StyleTitle,
  StyledInput,
  StyledMovieCard,
  StyledMovieList,
  StyledSearchView,
  Poster,
  Overview,
  Title,
  Star,
} from "./Search.style";
import axios from "axios";
import { useDebounce } from "../../hooks/useDebounce";

export const Search = () => {
  const [searchTerm, setSearchTerm] = useState("");
  const [movies, setMovies] = useState([]);
  const apiKey = "d2cb276ab0ca7b65595d1e9a2fd4ea84";
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  const handleSearch = useCallback(async () => {
    if (debouncedSearchTerm) {
      try {
        const response = await axios.get(
          `https://api.themoviedb.org/3/search/movie`,
          {
            params: {
              api_key: apiKey,
              query: debouncedSearchTerm,
            },
          }
        );
        setMovies(response.data.results);
      } catch (error) {
        console.error("Error fetching movies:", error);
      }
    } else {
      setMovies([]);
    }
  }, [debouncedSearchTerm]);

  useEffect(() => {
    handleSearch();
  }, [handleSearch]);

  const calculateStars = (vote_average) => {
    const stars = Math.round(vote_average);
    return stars > 0 ? Array(stars).fill("⭐").join("") : "";
  };

  return (
    <StyleSearch>
      <StyleTitle>🎥 Find your movies!</StyleTitle>
      <StyledInput
        type="text"
        placeholder="Type to search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <StyledSearchView>
        <StyledMovieList>
          {debouncedSearchTerm &&
            movies.map((movie) => (
              <StyledMovieCard key={movie.id}>
                <Poster
                  src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
                  alt={`Movie Poster of ${movie.title}`}
                />
                <Title>{movie.title}</Title>
                <Star>{`Rating: ${calculateStars(movie.vote_average)}`}</Star>
                <Overview>
                  <p>{movie.overview || "No overview available."}</p>
                </Overview>
              </StyledMovieCard>
            ))}
        </StyledMovieList>
      </StyledSearchView>
    </StyleSearch>
  );
};

 

기존 코드에

  const debouncedSearchTerm = useDebounce(searchTerm, 500);

 

이렇게 검색할 값과 시간을 추가해준다

 

기존 api를 가져오던 useEffect 훅에서 쿼리 값을 추가한 훅으로 변경해준다

조건문과 의존성 배열도 seatchTerm =>  추가한 훅으로 변경하기!

이런식으로~~

 


실행을 보면 무슨 차이인지 감이 딱 온다!

 

수정 전

 

수정 후

 

 


사용자가 입력을 할 때마다 API 요청이 아닌 시간 차를 두어서 api 요청을 하는 것이 바로 debounce!

재밋당

728x90