submit event handler에 debounce를 적용하여 서버와의 통신 최소화하기

HongBeen Lee·2022년 3월 21일
0

상황

현재 구현되어있는 상황은 다음과 같다.
input에 포커스되어있는 상황에서 [enter]를 누르거나, [submit]버튼을 누르면 onsubmit이벤트가 발동되어 서버에 저장되도록 구현했다. 그리고 성공적으로 데이터베이스에 저장되었다면 snackbar형식으로 알림을 띄워준다.

이때, 사용자가 [enter]나 [submit]버튼을 연타할 경우를 대비하여 가장 최신에 발생한 이벤트를 한 번만 핸들링하도록 변경하려고 한다.
이렇게하면 서버와의 통신을 최소화하면서도 snackbar알림도 한번만 보여줄 수 있다.

debounce가 적절한가

현재 redux-saga를 이용 중이기 때문에 해당 saga에서 takeLatest로 하는 방법도 있다. 하지만 saga까지 갈 것도 없이 클라이언트 단에서 바로 처리해주는 것이 가장 직접적이고 빠르다.
그래서 방법을 찾아보다가 throttle, debounce라는 키워드를 알게되었다.

일단 throttle과 debounce가 굉장히 헷갈리는데 정리해서 설명하자면 이렇다.

throttle

일정 시간(ex. 500ms)동안 발생되는 여러 개의 이벤트 중 가장 최신 이벤트를 발동시킨다.
즉, 500ms에 한번씩 이벤트를 발동시키게 된다.
예를 들어, 자동완성 기능에서 자동 완성된 키워드들을 가져와야 할 때, 일정 시간에 한 번씩 api를 가져오도록 할 때 사용된다.

debounce

일정 시간(ex.500ms)동안 이벤트가 일어나지 않는다면, 마지막 이벤트를 발동시킨다.
즉, 일정 시간을 기다리던 중에 이벤트가 발생하면 다시 일정 시간을 기다리게 된다.

둘 중에 적절한 건 debounce

나의 경우에 submit이벤트를 한 번만 발동시키고 싶기 때문에 debounce를 적용하는 것이 적절하다.
사용자가 여러번 submit이벤트를 발동시키더라도 딱 한 번만 발동시켜서 value를 서버에 전달해주면 된다.

적용하기1

적용하기에 앞서, web API인 setTimeout()의 리턴값에 대해 알아야 한다.


리턴하는 양수는 setTimeout()로 만든 타이머의 ID값이다.
그리고 이 타이머를 취소하려면 clearTimeout()함수의 인자로 타이머의 ID값을 전달해준다.

handleSubmit함수 안에 타이머를 넣어 구현하도록 한다.

const [submitTimer, setSubmitTimer] = useState<NodeJS.Timeout>();

const handleSubmit = useCallback((event : React.FormEvent |  React.FocusEvent)=>{
    event.preventDefault();
  
  	/* 예외처리 코드들 */
  
    submitTimer && clearTimeout(submitTimer);

    const newTimer = setTimeout(()=>{
      saveTask(id,inputValue);
    },500);
    setSubmitTimer(newTimer);

  },[saveTask,deleteTask,id,inputValue,title,submitTimer])

내 코드에서 saveTask()함수가 직접적인 서버와 통신을 하는 함수이다.
그래서 submit이 되었을 때, 함수 상단에서 예외 처리를 해주고 정말 save해야하는 경우에만 saveTask() 함수를 동작시킨다.

  1. state로 타이머ID를 관리한다.
  2. submit이 되면 타이머를 초기화시키고, 800ms를 기다린 후에 saveTask()를 동작시킨다.
  3. 800ms를 기다리던 중에 또 submit이 되면, 2번을 반복한다.

결국, 서버와 통신하는 saveTask()는 온전히 800ms를 기다린 후에야 동작된다.

+) 적용한 결과를 gif로 보여주기 어려워서, 적용하기2 를 이어 작성한다.

적용하기2

다른 곳에서도 유용하게 사용했다.
위 gif는 add버튼을 여러번 연타했을 때 디바운스가 잘 적용된 결과이다 👍

아래 코드에서는 어떤 자식요소를 추가하는 경우에서 add버튼 클릭에 적용한 코드이다.

const [addTimer, setAddTimer] = useState<NodeJS.Timeout>();

  const handleAdd = (()=>{
    if(!onAdd) return;
    addTimer && clearTimeout(addTimer);

    const newTimer = setTimeout(onAdd,400);
    setAddTimer(newTimer);
  })

여기에서는 onAdd 콜백함수가 optional prop으로 들어오기 때문에, 없는경우를 예외처리해주었다.

시간 지정

500ms는 내가 임의로 정한 값인데, 너무 짧으면 debounce의 역할이 줄어들고 너무 길면 사용자 입장에서 인터렉션이 원활하지 않다고 느낄 것이라고 판단했다. 800ms, 1000ms 등 실험해본 결과 500ms가 적당했다.

위의 두번째 적용한 곳에서는 무작위 연타를 방지하는 목적이었기 때문에 400ms로 적용했다.

아쉬운 점

위에 add버튼에 잘 적용이 되었으나, 즉시 인터렉션이 일어나지 않는 부분은 아쉽다.
그 원인은 add하자마자 서버에 추가하는 로직으로 구현했기 때문이다.
서버에 추가하지 않고, 목업데이터를 추가하여 보여준다면 서버에 저장되지 않기 때문에 디바운스를 적용할 필요도 없다. submit에만 신경쓰면 된다.

Ref

profile
🏃🏻‍♀️💨

0개의 댓글