디바운스, 스로틀이 제대로 동작하지 않는 이유

Debounce 와 Throttle이란?

검색을 해보면 이미 사용 예시와 설명이 자세히 나와있다.
이벤트를 제어(제한)하는 방법으로,과도한 이벤트 횟수의 실행으로 이벤트 핸들러가 무거운 계산 및 기타 DOM 조작과 같은 작업을 수없이 많이 수행하는 경우 성능 문제가 발생하고 이는 사용자 경험자 경험을 망가뜨릴 것이다.이를 해결하기위해 콜백의 실행횟수를 제한하기위해 사용함.

디바운스

이벤트를 그룹화하여 특정시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술
즉, 순차적 호출을 하나의 그룹으로 "그룹화"할 수 있다. 포인트는 맨 마지막 이벤트가 일어나고 일정시간 후에 딱 한번 실행된다!

스로틀

Throttle 은 이벤트를 일정한 주기마다 발생하도록 하는 기술
예를 들어 Throttle 의 설정시간으로 1초 를 주게되면 해당 콜백은 1초 동안 최대 한번만 실행됨. 1초 전에 쌓인 이벤트큐들은 모두 무시된다고 한다.

상황에 따라 어느 것이 유용할지 구분해야하지만 이미 잘 정리해놓은 블로그들이 많으니 그때마다 참조하는것도 좋을 것 같다 ㅎ

이번 포스팅에서는 실제 구현에 초점을 맞춰서 작성해보자!

디바운스 구현

보통 리액트에서는 lodash 패키지를 설치하여 사용한다.

import React from 'react'
import {firestore} from '../shared/firebase';
import {throttle,debounce} from 'lodash';

function Test() {
    const [text,setText] = React.useState('');

    const _debounce = React.useCallback(debounce(()=>{
        console.log('debounce콜백호출');
    },2000),[text]);
    
    const changehandler = (e) => { 
        _debounce();    
        setText(e.target.value)
    }
     
    React.useEffect(() => {
        return () => {
        	// debounce를 해제 해주어야합니다.
        },[]),
          
    return (
        <div>
            <input value = {text} onChange={changehandler}></input>
        </div>
    )
}

export default Test

사용할 때는 반드시 useCallback으로 디바운스 함수의 재생성을 막아야한다.

참고: https://dmitripavlutin.com/react-throttle-debounce/

위 예시는 제대로 동작하지 않는다(useCallback의 의존성 때문에).
테스트로, 글자를 계속 쳐보자, 첫 타이핑을 하고, 2초뒤에 밀린 콜백이 실행되어 연속적으로 디바운스함수가 실행된다.

why?

이유는 리렌더링에 있다. text를 칠때마다 state값이 바뀌면서, 컴포넌트 리렌더링을 유발하는데, 리렌더링 될때마다 _debounce함수가 재생성 되기 때문이다.(dependency가 text 이므로) 이벤트 큐를 살펴보면,
글자를 칠 때마다 inputChangeHandler가 호출되고, 요 함수에서 _debounce()가 호출된다. 그리고 _debounce()가 이벤트 큐에 쌓일 것이다.
해당 렌더링시점에 새롭게 생성된 디바운스함수가 큐에 쌓일것이고, 이를 같은 디바운스함수로 인지하지못하기 때문에, 이전 렌더링시점에 이벤트 큐에 담겨있던 _debounce()를 무시하지 못하고 이벤트루프에 의해서 콜스택으로 불려진다. 즉 친 글자 수 만큼 디바운스함수가 호출된다.

해결방법 (의존성의 중요성)

    const _debounce = React.useCallback(debounce(()=>{
        console.log('debounce콜백호출');
    },2000),[]);

useCallback의 의존성을 비우자. [] 로 설정하면 맨 처음 생성된 디바운스함수만 이벤트 큐에 쌓일것이다.
의존성을 적절히 사용하면 debounce함수내에서 컴포넌트의 상태값을 사용할때, refresh된 closure로 상태값에 접근할 수 있다.

위 경우에서는 디바운스 함수내에서 input의 text값을 사용하고싶다면, 매개변수로 상태값을 전달해서 사용하면 된다.

CleanUp을 꼭 해주자

클린업 함수에서, _debounce.cancel()을 달아주자.
컴포넌트가 언마운트 될 때, 직전 렌더링에서 생성된 debounce함수가 이벤트큐에 대기중이라면 삭제해주어야 불필요한 호출을 막을 수 있다.

다음편에서는 스로틀을 이용해서 무한스크롤을 구현해보도록 하자

좋은 웹페이지 즐겨찾기