A modern JavaScript utility library delivering modularity, performance & extras.
모듈화, 성능 및 기타 기능을 제공하는 자바스크립트 유틸리티 라이브러리
Lodash 공식 문서
Lodash 라이브러리는 JavaScript의 유용한 유틸 함수를 제공함으로써 빠른 작업에 도움을 주는 기능들을 가지고 있다. debounce와 throttle을 직접 구현할 수도 있지만 간단하게 lodash를 이용해보기로 했다.
debounce와 throttle은 일정 시간 동안 반복적으로 실행되는 코드의 성능을 향상시키기 위해 널리 사용되는 두 가지 기술이다. 두 기능 모두 자주 사용되는 이벤트나 함수의 실행 빈도를 제한하여, 성능 상의 유리함을 가져오기 위한 개념이라 할 수 있다.
예를 들어, keyboard가 한 글자씩 입력될 때마다 매번 api 요청을 보내 데이터를 가져온다면, 사용자의 의도와 무관한 요청이 자주 발생할 것이다. 이러한 요청을 적절히 조절하기 위해 입력이 끝난 후나, 입력되는 시간을 제어해 요청을 보내고 데이터 값을 가져온다면, 위와 같은 문제를 방지하고 성능을 개선할 수 있다.
디바운스는 시간이 많이 소요되는 작업이 너무 자주 실행되어 웹 페이지의 성능이 지연되지 않도록 하는데 사용되는 프로그래밍 방식이다. 선언된 함수는 지정된 시간 동안 호출이 중지될 때까지 모든 호출을 무시한다. 함수가 다시 실행되기 전에 특정 시간을 기다리도록 하는 것이다. 즉, 함수가 호출되는 속도를 제한한다.
_.debounce(func, [wait=0], [options={}])
debounce는 이벤트가 끝난 뒤에 설정해둔 시간(wait)이 지나야 콜백(func)이 실행된다.
사용 예:
자동 완성 — 종종 검색 박스는 사용자의 현재 입력에 대한 자동 완성 옵션을 제공하는 드롭다운을 제공하고, 때때로 제안된 항목은 API를 통해 백엔드에서 가져온다. 여기서 디바운스는 사용자가 텍스트를 제안하기 전에 몇 초 동안 입력을 중지할 때까지 기다리는 암시적인 텍스트를 구현하는 데 적용될 수 있다. 따라서 모든 키 입력에서 제안을 제공하기 전에 몇 초 동안 기다린다.
기타:
1. resize이벤트 핸들러 디바운싱
2. 자동 저장 기능에서 저장 기능 디바운싱
3. 사용자가 드래그 앤 드롭하는 동안 아무 것도 하지 않게 처리
4. 사용자가 입력을 멈출 때까지 Axios 요청을 하지 않게 처리
함수를 스로틀한다는 것은 함수가 지정된 시간 동안 최대 한 번(예: 10초마다 한 번) 호출되도록 하는 것을 의미한다. 즉, "최근에" 실행된 기능이 실행되지 않도록 조절한다. 또한 고정된 속도로 정기적으로 실행되도록 한다. 특히 scroll, mousemove 이벤트와 같이 짧은 시간에 굉장히 많이 실행되는 이벤트에 사용한다.
_.throttle(func, [wait=0], [options={}])
throttle은 콜백 함수(func)를 일정 주기(wait) 내에 한 번만 호출한다.
예:
게임 — 액션 게임에서 사용자는 종종 버튼을 눌러 주요 동작을 수행한다(예: 슈팅, 펀치). 그러나 모든 게이머가 알고 있듯이 사용자는 종종 필요한 것보다 훨씬 더 많은 버튼을 누르는데, 이는 액션의 흥분과 강도 때문일 것이다. 따라서 사용자는 5초 동안 펀치를 10번 칠 수 있지만 게임 캐릭터는 1초에 한 번만 펀치를 던질 수 있다. 이러한 상황에서는 작업을 제한하는 것이 좋다. 이 경우 펀치 동작을 1초로 조절하면 매초 두 번째 버튼 누름이 무시된다.
스크롤 이벤트 핸들러 - 스로틀의 또 다른 응용 예시는 사용자가 계속 스크롤하는 Facebook 및 Twitter와 같은 콘텐츠 로딩 웹페이지에 있다. 이러한 시나리오에서 스크롤 이벤트가 너무 자주 발생하면 많은 비디오와 이미지가 포함되므로 성능에 영향을 미칠 수 있다. 따라서 스크롤 이벤트는 스로틀을 사용해야 한다.
디바운스와 스로틀의 가장 큰 차이점은 디바운스는 사용자가 특정 시간 동안 이벤트를 수행하지 않은 경우 함수를 호출하는 반면, 스로틀은 사용자가 이벤트를 수행하는 동안 지정된 시간 간격으로 함수를 호출한다는 것이다. 결론적으로 디바운스는 최종 상태에 관심이 있을 때 활용 가능하며, 스로틀은 모든 중간 상태를 제어된 속도로 처리하려는 경우 유용하게 사용된다.
developic의 검색 기능을 구상할 때, 사용자가 꼭 엔터를 입력할 때가 아니라 검색어를 입력하는 과정에서 관련 결과가 출력되도록 하고 싶었다. 그러나 만약 디바운스를 적용하지 않는다면 한 글자 한 글자 마다, 완전히 의미없는 검색어의 경우에도 api 요청을 보내 불필요한 통신을 낭비하게 될 것이다. 따라서 다음과 같이 debounce를 활용해 도움을 얻고자 했다.
const router = useRouter();
const [keyword, setKeyword] = useState(router.query.keyword as string);
// debounce 함수: searchValue 인자를 넘겨받아, 500ms의 디바운스를 걸고 router.push기능으로 keyword로 넘겨준다. 이 키워드의 변화를 관찰해 각각 상황에 맞는 검색 api를 요청한다.
const debounce = useCallback(
_debounce(searchValue => {
router.push(`${router.pathname}?keyword=${searchValue}`);
}, 500),
[router.pathname]
);
// onChangeKeyword 함수: input 태그에 입력되는 값으로 keyword 상태를 업데이트하고, debounce 함수의 인자로 전달한다.
const onChangeKeyword = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setKeyword(e.target.value);
debounce(e.target.value);
}, []);
const onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
}
};
return (
<Layout>
<Head>
<title>검색</title>
</Head>
<SearchPageWithNavContainer>
<div className="title">
<TitleLabel title="검색" desc="Search" />
</div>
<SearchInput
value={keyword || ''}
onChange={onChangeKeyword}
onKeyPress={onKeyPress}
/>
<div className="block" />
<SearchPageNavigation />
{children}
</SearchPageWithNavContainer>
</Layout>
);
우선 우리 프로젝트의 검색 기능에는 다수의 옵션 설정이 있어 키보드 입력이 발생하면 바로 그 값으로 api 요청을 보내는 게 아니라 router
기능을 이용해 query.keyword
값을 변경하도록 구성되어 있다. 이 과정에서 500ms의 디바운스를 걸어 그 시간 안에 keyword의 변경이 다시 발생하면, 시간을 초기화하고 다시 기다리다가 이벤트가 끝났을 때 가장 최신 value로 keyword 값을 변경한다. 그리고 이 keyword 값 + 다른 조건을 받아 dispatch로 api 요청을 보낸다.
이런 식으로 적절하게 사용하면 api call 횟수나 이벤트 호출을 줄임으로써 리소스 절약에 도움이 되는 것을 확인할 수 있다.