[React] Debounce 구현하기

hyeondoonge·2021년 9월 13일
1

1. lodash의 debounce

아래의 코드는 keyword가 갱신될 때 마다 onChange함수가 등록이 되며 예상가능한 동작 방식이다.
하지만, keyword 외 컴포넌트 내부의 다른 state들이 변경되어 컴포넌트가 재렌더링 되면, 예상치 못한 순간에 onChange의 함수가 재생성되어 등록될 수 있는 문제점을 발견할 수 있었다.

쉽게 말해, onChange의 함수가 keyword외의 다른 state의 변경으로부터 재등록될 수 있다는 것이다.

import { useEffect } from "react";
import { debounce } from "lodash";

export default function App() {
  const [keyword, setKeyword] = useState("");

  const handleInput = ({ target }) => {
    setKeyword(target.value);
  };

  return (
    <div>
      <input type="text" onChange={debounce(handleinput, 550)} />
      <span>You typed {keyword}</span>
    </div>
  );
}

이를 위해, useMemo를 사용하여 리액트에 활용할 수 있다.
아래 코드처럼 메모된 debounce는 컴포넌트의 상태들이 변경되어 재렌더링되더라도 영향을 받지 않게된다.

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

export default function App() {
  const [keyword, setKeyword] = useState("");

  const handleInput = ({ target }) => {
    setKeyword(target.value);
  };

  const debouncedHandleInput = useMemo(() => {
    return debounce(handleInput, 550);
  }, []);

  useEffect(() => {
    return () => {
      debouncedHandleInput.cancel();
    };
  }, []);

  return (
    <div>
      <input type="text" onChange={debouncedHandleInput} />
      <span>You typed {keyword}</span>
    </div>
  );
}

2. custom hook을 이용한 debounce

setTimeout과 clearTimeout과 함수를 사용하여 직접 debounce 함수를 구현할 수 있다. 그리고 한 컴포넌트 내부에서 state변경에도 끄덕없는 useRef를 사용하여 timer를 만들어주었다.

timer.current는 활성화되어있을 때 즉, 이벤트가 발생하는 중에는 항상 값이 있게 되며 그러지 않을 때는 null값을 가진다.

그리고 컴포넌트가 unmount될 때 timer.current에 값이 있을 때를 대비해 timer를 해제하도록 해주었다.

+내가 정의한 debounce는 callback에 event객체가 전달되지 않기 때문에 event가 필요한 처리는 callback함수를 정의하는 곳에서 해주어야한다.

import { useEffect, useRef } from 'react';

export default function useDebounce() {
  const timer = useRef(null);

  const debounce = (callback, time) => {
    if (timer.current) clearTimeout(timer.current);
    timer.current = setTimeout(() => {
      callback();
      timer.current = null;
    }, time);
  };

  useEffect(() => {
    return () => {
      if (timer.current) clearTimeout(timer);
    };
  });

  return debounce;
}

🧸 여기서부턴 잡담 - ! 🧸

외부 모듈을 사용하면 되지만 굳이 내가 커스텀 훅을 만들어서 사용한 이유는,
일단 외부 모듈에 최대한 의지하고 싶지 않았고
내부 코드가 어떻게 구현되어있을까, 내가 스스로 만들 수 있을까? 에 대한 호기심이 생겼었다.
lodash것과 달리 적합하지 않은 파라미터가 들어왔을 때의 예외처리나 cancel, flush 같은 다양한 함수들을 제공하진 않아 추후 활용성이 낮을 것 같다. 하지만 일단은 위의 debounce로 충분하다 생각하여 현재 프로젝트에서는 이 debounce를 재사용할 예정이다.

0개의 댓글