KAKAOMAP API를 이용해 프로젝트를 진행 중이다.
지난 프로젝트에서 카카오맵 API를 사용했지만, 좌표를 props로 받아와 띄우는 정적인 지도에 지나지 않았다. 그래서 이번에 동적인 지도를 구현하는 데에 있어서, 렌더링이나 성능 개선에 더욱 신경을 쓰면서 개발하는 데에 초점을 맞췄다.
(버벅이는 지도 상상하기도 싫엇 😭..)
특히 지도 API에서 지도를 드래그하거나 줌인, 줌아웃을 하면서 여러 기능을 수행하게 된다. 이 과정에서 상당히 빈번한 렌더링이 실행되기 때문에, 예전부터 곁눈질로 봐오던 Lodash의 throttle과 debounce를 적용하기로 했다.
throttle은 지도 맵의 렌더링에 적용하였고, debounce는 쿼리 검색 기능에 적용하였다.
A modern JavaScript utility library delivering modularity, performance & extras.
모듈화, 성능 및 기타 기능을 제공하는 자바스크립트 유틸리티 라이브러리
Lodash 공식 문서
공식 문서를 보면 정말 깔끔하게 설명이 잘 되어 있고, 다음에 쓰고 싶은 기능들도 많이 보인다.
과거에는 underscore 라이브러리가 많이 쓰였지만, 현재는 lodash가 더 많이 쓰이고 성능이 뛰어나다는 평가를 받는다.
debounce와 throttle은 이벤트를 반복적으로 실행할 때, 콜백 함수의 불필요한 실행을 줄여주는 역할을 한다. 불필요한 서버 리퀘스트를 막을 수도 있고, 불필요한 통신을 줄임과 동시에 필요 없는 렌더링 또한 막을 수 있어 컴포넌트의 성능 개선에도 도움을 준다.
특히 외부 API를 사용할 경우 일일 할당량에 제한이 있는 경우가 있는데, 과도한 서버 요청을 막아줄 수 있다는 면에서 필수적으로 사용해야 할 기능이라고 생각한다.
(나야 나.. YOUTUBE API 일일 할당량 초과해본 사람 🙋🏻♀️)
_.debounce(func, [wait=0], [options={}])
debounce는 이벤트가 끝난 뒤에 설정해둔 시간(wait)이 지나야 콜백(func)이 실행 된다. 아래 사진을 보면 여러 이벤트들이 하나로 그룹지어지는데 이게 바로 debounce다.
_.throttle(func, [wait=0], [options={}])
throttle은 콜백 함수(func)를 일정 주기(wait) 내에 한 번만 호출한다. 특히 scroll, mousemove 이벤트와 같이 짧은 시간에 굉장히 많이 실행되는 이벤트에 사용한다.
먼저 일반적인 검색 input 태그에 onChange로 handleSearch func를 달아 console.log(query)로 찍힌 결과를 보자.
신사역을 검색하는 데에 자그마치 9개의 query event가 발생했다.
다음은 debounce를 적용해보자.
먼저 import _ from 'lodash'
를 해준다. 그리고 아래와 같이 input에 ref를 달아준다.
onChange 이벤트에 handleSearchChange
를 달아주되, delayedQueryCall
(debounce)을 거쳐 sendQuery
(axios)해서 불필요한 통신을 막아준다.
'신사'를 검색할 때 불필요하게 검색이 되었던 'ㅅ', '시', '신ㅅ'같은 쿼리는 없고, 정확하게 '신사' 혹은 '신사역', '신'을 검색할 수 있게 되었다.
useEffect()의 current property로 접근하는 것이 불필요하다고 여겨질 경우 다음과 같이 리팩토링할 수 있다.
const delayedQueryCall = useCallback<any>(
_.debounce((q) => sendQuery(q), 500), []);
지도 또한 마찬가지이다. 먼저 throttle을 적용하지 않은 결과다.
스크롤 이벤트, 드래그 이벤트가 이렇게 무시무시하다.
불필요한 렌더링과 서버 요청을 막기 위해, 지도의 좌표를 찾는 addListener(카카오맵 API)에 _.throttle
을 적용해주었다.
결과를 보자.
콘솔창을 보면 확연히 다른 결과가 느껴진다.
(뭐랄까.. 촐랑대지 않고 절도있는 느낌이랄까..ㅎㅎ🤷🏻♀️)
debounce는 이벤트가 끝날 때까지 기다렸다가 시작된다는 점, throttle은 이벤트가 시작되면 일정 주기로 계속 실행한다는 점이 다르다.
따라서 확실한 성능 개선을 위해서는 debounce를 사용하여 이벤트를 한 번만 실행되도록 하는 것이 효과적일 것이다.
하지만 유저가 즉각적인 결과를 요하는 기능에 있어서는 throttle을 사용하여 유저를 배려합시다!
다음에는 lodash의 다른 메소드들도 사용해보고 후기를 남겨보겠다.
참고자료
Lodash 공식 문서
Debouncing and Throttling Explained Through Examples
Using throttle and debounce in a React function component
throttle & debounce 폴리필
React 렌더함수 최적화시에 Debounce 혹은 Throttle을 응용하는 정도로도 대게 충분한 경우가 많지만 timeout을 각 유저별로 최적화 하기 힘들고 최적화된 timeout 값을 찾기도 힘들고, 브라우저의 Frame rate와 Throttling된 event rate가 차이날 경우 지터링 등의 문제로 UX를 해치기도 합니다. 그래서 최근에는 사용자 인터랙션에 따른 애니메이션 최적화 시 requestAnimationFrame이라는 브라우저 API를 사용해 event 주기를 사용자 모니터의 주사율과 동기화하는 방식을 많이 사용합니다. React에서 사용이 편리하도록 Hook 형태로 캡슐화된 raf-schd라는 라이브러리도 존재하니 궁금하신 분들은 한번 참고해 보세요~
👍