아래의 코드는 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>
);
}
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를 재사용할 예정이다.