본문 바로가기
{Javascript}

03.08.23{개발일기} debounce 활용 계좌번호 예외처리 UI 성능 최적화하기

by Davey 2023. 3. 8.
728x90

 

서론


2023년 1월 30일부터 3월 7일까지 '오늘의집사'라는 프로덕트를 개발하면서 만났던 개발이슈 중, 마이페이지 계좌관리 기능의 계좌등록 기능을 구현하면서 은행별 계좌번호 입력갯수에 따른 예외처리 이벤트 핸들러 함수가 필요했습니다. 계좌번호 입력시 input 이벤트가 짧은시간 연속적으로 발생하는 상황속에, 은행별 계좌번호 입력칸마다 부족한 숫자 자릿수만큼 'X글자를 입력하세요!'라는 알람 문구를 노출 시켜야하는 요구사항이 있었습니다. 이로인해 계좌번호 입력시 input 이벤트가 과도하게 짧은시간 연속해서 해당 이벤트 핸들러 함수가 호출되므로 서비스 성능에 치명적인 이슈가 발생할 가능성이 다분하여 고민하게 되었습니다. 금번 포스팅에서는 이러한 문제를 어떤 관점으로 접근했고 어떻게 해결하였는지에 관한 이야기를 풀어보려고 합니다.

 

 

input 입력할때마다 아닌 input이벤트가 가장 마지막에 실행된 순간에만 안내문구가 노출되도록 debounce 설정한 모습

 

문제접근과정 및 자료조사


일반적으로 scroll, resize, input, mousemove 와 같은 이벤트에 바인딩한 이벤트 핸들러는 과도하게 호출되어 성능에 문제를 일으킬 가능성이 다분합니다. 적절한 타이밍에 이벤트 리스너 할당을 제거하는 방법을 통해 성능을 높일 수 있는 방법은 MDN에도 나오지만, debouncing이나 throttle같은 구체적인 내용은 언급되어 있지 않았습니다. 앞서 제목에 적혀있는 lodash도 적절한 setTimeout과 아래 WEB API를 활용해서 구현된 라이브러리라는 것을 대략적으로 짐작해볼 수는 있었습니다.

 

 

출처: MDN

 

 

프로젝트와 병행했던 저자 이웅모님의 '모던 자바스크립트 Deep Dive' 책자 스터디에서 41.3장에 '디바운스와 스로틀'에 관한 정의와 예시코드가 올바르게 나와 있었습니다. 물론 요즘엔 lodash를 실무에선 많이 사용하지만, 라이브러리 없이도 직접 구현하는 방법 또한 알고 있어야 라이브러리를 활용할때 더 올바르게 이해하고 사용할 수 있다라고 판단했고, 라이브러리 문서대신 직접 debounce 및 throttle 코드를 로우레벨로 작성하는 법부터 확인하게 되었습니다. 우선 제일 먼저 고려해볼 항목은 'debounce를 사용할것인가? throttle을 사용할 것인가?'이었습니다. 

 

 

모던자바스크립트 딥다이브 책자에 정의된 디바운스 내용을 인용해보겠습니다. '디바운스는 짧은시간 간격으로 이벤트가 연속해서 발생하면 이벤트 핸들러를 호출하지 않다가 일정 시간이 경과한 이후에 이벤트 핸들러가 한번만 호출되도록 한다. 즉, 디바운스는 짧으시간 간격으로 발생하는 이벤트를 그룹화해서 마지막에 한번만 이벤트 핸들러가 호출되도록 한다.'라고 정의되어 있습니다. 그러면 스로틀은 무엇일까요? '스로틀은 짧은 시간 간격으로 이벤트가 연속해서 발생하더라도 일정 시간 간격으로 이벤트 핸들러가 최대 한번만 호출 되도록 한다. 즉, 스로틀은 짧은 시간 간격으로 연속해서 발생하는 이벤트를 그룹화해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만든다'라고 정의되어 있습니다. 스로틀과 디바운스의 정의를 보면 '일정시간단위'와 '한번만 호출'이라는 키워드가 유사해보이지만 아래 그래프를 참조해보면 완전히 다른 내용임을 알 수 있습니다.

 

 

출처:https://web.archive.org/web/20180324022838/http://demo.nimius.net/debounce_throttle/

 

 

마우스이벤트를 특정 구역에서 발생시켰을때 이벤트 발생시점부터 마우스가 구역을 벗어나기 전까지 이벤트 핸들러 호출 발생빈도수 차이를 주목해주세요. 확실히 debounce나 throttle을 적용하지 않은 일반 이벤트리스너 핸들러 함수 호출의 경우 확연히 마우스가 이동한 시점마다 호출되고 있는것을 확인할 수가 있습니다. 이와 달리 debounce의 경우 최초로 호출된 이후 마우스가 일정시간 움직이지 않으면 이벤트 핸들러가 한번만 호출되는 녹색선을 확인할 수 있었습니다. throttle의 경우 개발자가 설정해놓은 일정시간 단위로 이벤트 핸들러가 호출되는 것을 주황색 선을 통해 확일 할 수 있습니다. 성능면으로 볼때 debounce가 가장 우수하다고 판단했고, 또 현재 요구사항에도 debounce가 가장 적합하다고 결론지을 수 있었습니다.

 

 

도구 선택과정


 

우선 라이브러리를 사용하기에 앞서, debounce를 직접 구현하는 부분은 '모던 자바스크립트 Deep Dive' 책자 예제41-05 부분을 참조하여 재구성하여 작성해보았습니다. 책자에 첨부된 debounce를 구현하는 예시코드는 간략하게 구현되어있었으므로 완전하지 않지만, 대략적인 흐름을 확인할 수 있었습니다.

 

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// `wait` milliseconds.
const debounce = (func, wait) => {
  let timeout;

  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
 
 const alertWords = document.querySelecter('.alert-words')
 document.querySelecter('.account-input').addEventListener('click', (e) => {
	debounce(e => {
    alertWords.innerText = `{e.target.dataset.id}글자를 입력하세요!`
    }, 300)
 
 })

 

 

위와같은 흐름을 파악한 후, UnderScore의 debounce함수와 lodash debounce함수 둘 중 어느것을 선택하는 것이 나을지 고민되었습니다. 두개의 라이브러리 문서를 각각 비교하였을 때, 기능면에서는 둘다 clear() 기능도 있고 제 요구사항을 충족해주었습니다. 다만, 예시 샘플 코드가 lodash가 비록 jquery로 작성되어 있긴 했지만, 샘플코드도 명확하게 작성되어 있었고 다운로드 횟수도 더 많은 lodash를 좀더 대중적이라고 판단하여 선택하게 되었습니다.

 

한주에 1000만명이 다운받는 underscoe / 출처: npm

 

하지만 문서화도 잘되어 있고, 약 5천만명이 다운받는 좀 더 대중적인 lodash를 선택함 / 출처: npm

 

문제해결 예시코드


npm lodash 문서를 참조하면 보통은 아래와 같이 변수를 선언하여 _.debounce() 형태로 사용하지만

 

// Load the full build.
var _ = require('lodash');

 

 

debounce만 import하여 사용했했고, global이 아닌 해당 레포지토리에만 npm으로 lodash 설치했습니다. 

 

$ npm i --save lodash

 

 

프로덕트 기능 구현을 위해 작성한 코드는 아래와 같습니다.

 

import { debounce } from "lodash";

function setInputAlertWord() {
	setTimeout(() => {
		if (document.querySelector(".input-alert-word")) {
			document.querySelector(".input-alert-word").innerText = "";
		}
	}, 1200);
}

const debounceOnKeydown = debounce((error) => {
	if (document.querySelector(".input-alert-word")) {
		document.querySelector(".input-alert-word").innerText = error;
	}
	setInputAlertWord();
}, 100);


function onKeyDown(e, accountBlock) {
	document.querySelector(".ic-tag-btn").style.opacity = "100%";

	if (isDigit(accountBlock.value) && e.key !== "Backspace" && e.key !== "") {
		debounceOnKeydown("숫자를 입력하세요!");
		return;
	}

	if (
		accountBlock.maxLength > accountBlock.value.length &&
		e.key !== "Backspace" &&
		e.key !== ""
	) {
		debounceOnKeydown(`${accountBlock.maxLength}글자를 입력하세요!`);
		return;
	}
}

...abbreviation

accountInputBlockList.forEach((block) => {
    block.addEventListener("keydown", (e) => {
        onKeyDown(e, e.target);
    });
});

...abbreviation

 

결론


성능최적화라는 부분이 주니어 입장에서 상당히 막연하고 어렵게만 느껴졌었는데, 디바운스를 활용하여 성능을 높이는 경험을 통해 성능 최적화 기법을 좀 더 여러 상황에서 고민해보고 직접 실천하는 것의 중요성을 깨닫게 되었습니다. input 요소 입력갓에 따라 axios 요청하는 입력 필드 자동완성 UI 구현 및, 버튼 중복 클릭 방지 처리 등에도 debounce를 활용해볼 수 있음도 알게 되었습니다. 이번엔 사용해보지 못했지만 throttle 함수의 경우, 두번째 인수로 전달한 요청 지연시간 delay가 경과 하기 이전에 이벤트가 발생하면 아무것도 하지 않다가 delay 시간이 경과했을 때 이벤트가 발생하면 콜백 함수를 호출하고 새로운 타이머로 재설정 함을 알게 되었고, 만약 이벤트가 발생하지 않으면 콜백함수를 호출하지 않는 사실도 알게 되었습니다. 이를 통해 무한 스크롤 UI 구현에도 왜 적용해보면 좋다고하는지 이유를 알게 되었습니다. 추후에 관련 UI 작업시 throttle을 적용해 볼 예정입니다.

 

 

참고링크

https://web.archive.org/web/20180324022838/http://demo.nimius.net/debounce_throttle/

 

728x90

댓글