[React Hooks 총정리] Debounce로 서버 비용 아끼는 방법 / Custom Hook

혜빈·2024년 7월 24일
0

REACT 보충개념

목록 보기
40/48

Debounce(디바운스)

  • 너무 자주 호출되는 함수로 인해 문제가 발생하는 경우가 있음
  • 특히 특정 이벤트의 반응에서 함수를 호출해야 할 때 이벤트가 극단적으로 많이 발생하면 문제가 발생함
  • 그렇게 되면 성능 저하, 많은 서버 비용 유발이 발생됨
    - 해결방법 -> Debounce(디바운스), Throttle(스로틀)
    - 함수가 너무 자주 호출되는 것을 방지해줌
    (호출 횟수에 제한을 두고 싶을 때 사용)

Debounce란?

  • 이벤트가 연속적으로 발생할 때,
    제일 마지막 이벤트가 발생한 후 일정시간이 지난 후에 함수를 호출함

  • 이렇게 짧은 시간에 이벤트가 수백번 일어난다면 함수도 수백번 호출될 수밖에 없음

  • Debounce를 사용하면 마지막 이벤트가 발생하고 일정시간동안 이벤트가 발생하지 않을 시 함수가 실행됨
  • 이벤트가 극단적으로 많이 발생한다고 해도 함수의 호출 횟수를 제한하여 훨씬 효율적으로 관리할 수 있음

실제 코드 구현

  • 사용자가 검색어를 입력하면 무엇을 검색할지 예측해서 자동완성된 결과를 보여주는 기능 구현
  • 사용자가 타이핑 할 때 실시간으로 결과를 보여줘야 함
import { useState, useEffect } from "react";
import "../DebounceHook.css";

// 함수에 사용자가 검색한 검색어를 넣어주면 검색어에 따라 필터링된 결과물을 반환
function fetchDataFromServer(value) {
  // 만약 value가 비어있으면 빈 배열 반환
  if (!value) {
    return [];
  }
  console.log("서버로부터 데이터를 가져오는중...");
  const users = [
    { name: "차은우", age: "27" },
    { name: "이동민", age: "23" },
    { name: "이동수", age: "32" },
    { name: "손흥민", age: "33" },
    { name: "조규성", age: "29" },
    { name: "최우식", age: "25" },
    { name: "차승원", age: "40" },
  ];
  // value로 시작하는 것들로만 필터링해서 반환
  return users.filter((user) => user.name.startsWith(value));
}
function DebounceHook() {
  // 검색어를 관리해줄 state
  const [input, setInput] = useState("");
  // 결과물을 관리해줄 state
  const [result, setResult] = useState([]);

  // input이 변경될때마다 반응
  useEffect(() => {
    const users = fetchDataFromServer(input);
    setResult(users);
  }, [input]);

  return (
    <div className="container">
      <div className="search-container">
        <input
          placeholder="검색어를 입력해주세요"
          value={input}
          onChange={(event) => setInput(event.target.value)}
        />
        <ul>
          {result.map((user) => (
            <li key={user.name}>
              <span>{user.name}</span>
              <span>{user.age}</span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default DebounceHook;

  • 화면은 잘 작동이 되지만, console을 확인해보면 함수가 많이 호출된 것을 볼 수 있음

  • 사용자가 타이핑을 할때마다 그 이벤트에 반응해서 fetchDataFromServer 함수를 호출하기 때문임
  • 만약 이 함수가 실제 서버와 통신을 하는 함수였다면 수천명의 사람들이 사용하기 때문에 함수는 굉장히 많이 호출이 될것이고,
    서버 비용이 많이 발생하며 서버가 과부하 걸릴 수 있음
    - 해결방법 -> Debounce 사용

Debounce 적용 코드

  • 현재는 사용자가 검색어를 입력하면 입력이벤트가 연달아서 발생함
  • 이벤트가 발생할때마다 함수를 호출해주는게 아니라
    사용자가 타이핑을 잠시 멈출 때 일정 시간을 기다린 후에 함수를 호출해주자
import { useState, useEffect } from "react";
import "../DebounceHook.css";

// 함수에 사용자가 검색한 검색어를 넣어주면 검색어에 따라 필터링된 결과물을 반환
function fetchDataFromServer(value) {
  // 만약 value가 비어있으면 빈 배열 반환
  if (!value) {
    return [];
  }
  console.log("서버로부터 데이터를 가져오는중...");
  const users = [
    { name: "차은우", age: "27" },
    { name: "이동민", age: "23" },
    { name: "이동수", age: "32" },
    { name: "손흥민", age: "33" },
    { name: "조규성", age: "29" },
    { name: "최우식", age: "25" },
    { name: "차승원", age: "40" },
  ];
  // value로 시작하는 것들로만 필터링해서 반환
  return users.filter((user) => user.name.startsWith(value));
}
function DebounceHook() {
  // 검색어를 관리해줄 state
  const [input, setInput] = useState("");
  const [debouncedInput, setDebouncedInput] = useState(input);
  // 결과물을 관리해줄 state
  const [result, setResult] = useState([]);

  // 사용자가 타이핑을 한 후 1초가 지난 다음 input안의 결과물을 debouncedInput에 넣어주기
  useEffect(() => {
    // setTimeout은 생성된 타이머의 식별자를 반환하는데, 식별자를 clearTimeout이라는 함수에 넣고 호출해주면 타이머를 취소할 수 있음
    const timerID = setTimeout(() => {
      console.log("콜백호출");
      setDebouncedInput(input);
    }, 1000);

    // useEffect의 return값으로 콜백함수를 넣어주면 콜백함수는 다음 useEffect가 실행되기 이전에 실행됨
    return () => {
      clearTimeout(timerID);
    };
  }, [input]);

  // input이 변경될때마다 반응
  useEffect(() => {
    const users = fetchDataFromServer(input);
    setResult(users);
  }, [input]);

  return (
    <div className="container">
      <div className="search-container">
        <input
          placeholder="검색어를 입력해주세요"
          value={input}
          onChange={(event) => setInput(event.target.value)}
        />
        <ul>
          {result.map((user) => (
            <li key={user.name}>
              <span>{user.name}</span>
              <span>{user.age}</span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default DebounceHook;

정리

  • 사용자가 타이핑을하면 입력 이벤트가 연달아서 발생
  • 그때마다 inputState가 업데이트 되기 때문에 Effect가 실행이 됨
  • Effect가 실행이 될때마다 setTimeout으로 인해 1초 타이머가 시작이 되지만,
    1초가 다 되기 전에 input이 또 업데이트됨으로 인해 그 다음 effect가 실행이 될 때
    우리가 이전 effect에서 반환해준 clearTimout함수가 먼저 호출이 되므로
    그 이전 타이머는 취소가 됨
  • 결국에는 가장 마지막으로 실행된 effect의 Timer만 진행이 되기 때문에 console.log("콜백호출")이 단 한번만 실행이 됨

  • debouncedInpu이 변경될때마다 반응하도록 코드 수정해주기

  • 연속으로 input에 입력을 해도 멈춘 후 1초 후에 딱 한번만 fetchData 함수가 실행이 됨
  • 수만명이 사용해도 서버 비용, 서버 과부화 문제가 발생하지 않음

재사용 가능한 Custom hook으로 만들기

  • 코드의 중복을 줄일 수 있고, 유지 보수성을 높일 수 있음

수정코드

DebounceHook

import { useState, useEffect } from "react";
import "../DebounceHook.css";
import { useDebounce } from "../hooks/debounceHook2";

// 함수에 사용자가 검색한 검색어를 넣어주면 검색어에 따라 필터링된 결과물을 반환
function fetchDataFromServer(value) {
  // 만약 value가 비어있으면 빈 배열 반환
  if (!value) {
    return [];
  }
  console.log("서버로부터 데이터를 가져오는중...");
  const users = [
    { name: "차은우", age: "27" },
    { name: "이동민", age: "23" },
    { name: "이동수", age: "32" },
    { name: "손흥민", age: "33" },
    { name: "조규성", age: "29" },
    { name: "최우식", age: "25" },
    { name: "차승원", age: "40" },
  ];
  // value로 시작하는 것들로만 필터링해서 반환
  return users.filter((user) => user.name.startsWith(value));
}
function DebounceHook() {
  // 검색어를 관리해줄 state
  const [input, setInput] = useState("");
  // 0.3초 딜레이
  const debouncedInput = useDebounce(input, 300);
  // 결과물을 관리해줄 state
  const [result, setResult] = useState([]);

  // debouncedInpu이 변경될때마다 반응
  useEffect(() => {
    const users = fetchDataFromServer(debouncedInput);
    setResult(users);
  }, [debouncedInput]);

  return (
    <div className="container">
      <div className="search-container">
        <input
          placeholder="검색어를 입력해주세요"
          value={input}
          onChange={(event) => setInput(event.target.value)}
        />
        <ul>
          {result.map((user) => (
            <li key={user.name}>
              <span>{user.name}</span>
              <span>{user.age}</span>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default DebounceHook;

커스텀 훅

import { useState, useEffect } from "react";

// value - 이벤트 발생으로 인해 변경되는 값 (여기에서는 input을 의미)
// delay - 마지막 이벤트가 발생하고 기다리는 시간을 의미
// debouncedValue - debounce가 적용된 값(debouncedinput을 의미)

// input이 아니라 다른 경우에도 Custom Hook을 사용할 수 있도록 debouncedInput 대신 debouncedValue사용
export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    // setTimeout은 생성된 타이머의 식별자를 반환하는데, 식별자를 clearTimeout이라는 함수에 넣고 호출해주면 타이머를 취소할 수 있음
    const timerID = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // useEffect의 return값으로 콜백함수를 넣어주면 콜백함수는 다음 useEffect가 실행되기 이전에 실행됨
    return () => {
      clearTimeout(timerID);
    };
  }, [value, delay]);

  return debouncedValue;
}

정리

**- Debounce는 함수의 호출 횟수를 제한하여 리소스 낭비를 줄여줌

  • 리소스를 낭비하지 않으면 프로그램의 성능도 높일 수 있고, 서버 비용도 절약할 수 있음**
profile
최강 개발자를 꿈꾸는 병아리

0개의 댓글