throttle
과 debounce
는 DOM 이벤트를 제어하는 방법으로, 자주 호출되는 이벤트의 실행 빈도를 줄여 성능상의 이점을 얻기 위해 사용된다.
[ 대표적인 사용 상황 ]
사용자가 입력창에 값을 입력할 때마다 상태값이 변하면서 리렌더링이 진행될 것이다.
그리고 이에 대해 예외 처리를 하는 함수가 그때마다 호출된다면, 굉장히 비효율적이며 그만큼 지연될 가능성이 크다.
또는 웹페이지를 구성하는데 해당 사이트는 다시 방문시 사용자가 읽던 포스트의 위치를 기억하고 그 페이지를 보여준다고 생각해보자.
페이지를 나눌 단위가 없으므로, 스크롤 단위로 페이지 좌표를 서버에 기록하고자 한다면, 가장 단순한 방법은 JavaScript의 Scroll 이벤트를 이용해서 좌표를 저장하고 기록하는 방식이다.
이 경우 사용자의 스크롤링마다 이벤트를 발생시키면 서버에도 클라이언트에도 매우 큰 부하가 발생하게 된다.
최악의 경우 페이지의 세로 좌표만큼 이벤트가 발생될 것이다.
예시는 최악의 구조와 최악의 경우를 가정한 것이지만, 중요한 점은 이벤트의 오버클럭(OverClock)
이 소프트웨어에 손상을 가져온다는 것이다.
이런 경우들의 처리를 위해서도 쓰로쓸링과 디바운싱 이라는 개념이 똑같이 적용되어 사용된다.
debounce
는 특정 시간이 지난 후에 한 번만 이벤트가 실행되도록 하는 것이다.
입력으로 예를 들면, 입력이라는 사용자의 이벤트가 발생하고 특정 시간이 지난 후 예외 처리를 한 번만 실행시키는 것이다.
즉, 이벤트 핸들러가 주기적으로 여러 개의 발생한 이벤트를 하나로 묶어서 처리하는 방식이다.
이때, 먼저 발생한 이벤트가 처리를 대기하며, 대기하는 도중 새 이벤트가 발생하면 이전 이벤트의 대기를 취소가혹 해당 이벤트를 기준으로 다시 처리를 대기한다.
이렇게 특정 시간 동안 처리는 대기하게 되며, 결과적으로는 일정한 시간 동안 연속적으로 발생한 이벤트는 마지막으로 발생한 이벤트를 기준으로 처리된다.
// src/hooks/useDebounce.tsx
import { useEffect, useState } from 'react';
export const useDebounce = (value: string, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
};
// 사용
const debouncedSearch = useDebounce(search, 500);
fetchNotifications(debouncedSearch, ...)
throttling
은 발생되는 이벤트 중간에 delay를 포함시킨다.
일정한 주기마다 이벤트를 발생시키며, 일정시간 동안 이벤트를 한 번만 실행한다.
즉, delay 이내로 연속적으로 발생한 이벤트에 대해서는 무시한다.
함수를 실행한 순간부터 일정 시간동안 다시 호출되지 않도록 하는 것이다.
사용 예시
React18에서는 업데이트 중에도 앱의 응답성을 유지하는데 도움이 되는 새로운 API를 도입한다.
이 새로운 API를 사용하면 특정 업데이트를 Transition
으로 표시하여 사용자 상호 작용을 크게 개선할 수 있다.
React를 사용하면 상태 전환 중에 시각적 피드백을 제공하고 전환이 발생하는 동안 브라우저의 응답성을 유지할 수 있다.
이 기능은 리액트에서 어떠한 업데이트가 urgent하며 어떠한게 그러하지 않은지 알려준다.
그래서 상태 업데이트의 우선순위를 주게 된다.
대표적으로 검색 기능을 구현할 때 검색하는 input은 이벤트에 따라서 리렌더링이 해당 화면에 엄데이트 되어야 한다.
하지만 그 아래 검색 결과도 이에 따라 업데이트 되어야 하는데, 검색 결과 리스트가 많지 않더라도 내부적으로 검색 결과를 가져오는데 많은 작업을 진행할 수 있기에 검색창에 타이핑을 하는 것에 따라 바로 바로 검색 결과도 업데이트하면 성능에 문제가 생길 수 있다.
그렇기에 이 부분은 검색 창과 결과 창 두 부분으로 나눌 수 있으며, 유저가 타이핑하는 것에 따라 즉각 반영되기를 기대하는 검색창, 그리고 검색창보다는 UI 업데이트가 느린 것에 자연스럽게 받아들여져야 하는 결과창으로 나눌 수 있다.
새로운 startTrransition
API는 업데이트를 'Transition'으로 표시할 수 있는 기능을 제공하여 이 문제를 해결한다.
이 API는 리액트에게 상태 업데이트에 대한 우선순위를 정해준다.
startTransition
에 래핑된 업데이트는 긴급하지 않은 것으로 처리되며, 클릭이나 키 누름과 같은 더 긴급한 업데이트가 들어오는 경우 중단된다.
전환이 사용자에 의해 중단되면 (예: 여러 문자를 연속으로 입력)
React는 다음을 throw한다.
완료되지 않은 오래된 렌더링 작업을 제거하고 최신 업데이트만 렌더링한다.
Transition을 사용하면, UI가 크게 변경되더라도 대부분의 상호 작용을 빠르게 유지할 수 있다.
또한 더 이상 관련이 없는 콘텐츠를 렌더링하는데 시간을 낭비하지 않아도 된다.
검색 창에 타이핑을 했을 때, startTransition
API로 인해 결과 창에는 UI 업데이트 우선순위가 밀려서 업데이트 보류가 일어날 때는, 아래와 같이 isPending
이 true
일 시에 Spinner
같은 컴포넌트를 보여주면 된다.
startTransition
이 없을 때는
👉 debounce를 이용하거나 setTimeout을 이용하는 것은 결국 모든 이벤트가 schedule되어 있고 뒤로 밀리는 것이기 때문에, 이벤트가 끝나고 계속 결과를 표출하게 된다.