너무 많은 API 호출
Kakao Map Api를 활용하여 프로젝트를 진행 중인데, 지도를 구현하던 중 버튼을 클릭하거나 지도 크기를 조정하는 등 화면이 리랜더링 됐을 때 처음 설정해 두었던 좌표로 다시 이동하는 문제가 있었습니다. 그렇기에 지도를 움직일 때마다 현재 위치를 재조정해주도록 코드를 수정하였는데, 좌표값이 이동할 때마다 현재 위치를 재조정해주니 아래 사진과 같이 너무 많은 API 호출이 발생하였습니다.
한번 움직일때마다 이렇게 많은 호출이 일어나면 짧은 시간에 너무 많은 데이터가 들어오기 때문에 웹의 성능이 저하될 가능성이 높습니다. 또한 카카오의 경우 무료 API라 괜찮지만 유료 API인 경우에는 많은 과금이 발생할 가능성도 있습니다.
저 같은 경우에는 이동이 완료된 이후의 위치만 알면 되기 때문에 lodash의 debounce를 활용하여 API 호출을 최소화해 주었습니다.
Lodash
lodash는 javascript의 가장 인기 있는 라이브러리 중 하나로 많은 개발자들이 lodash를 활용하여 개발을 진행합니다.
주로 array, collection, date 등 데이터의 필수적인 구조를 쉽게 다루기 위해 사용하고, 이외에도 여러 가지 기본적으로 제공되지 않는 편리한 메서드들을 제공하고 있습니다. 이러한 lodash의 메소드들은 _.(변수)와 같은 형태로 사용합니다.
Debounce
저는 이러한 lodash의 많은 메소드 중 debounce를 사용해 보았습니다. debounce를 사용하면, 특정 함수를 사용할 때, 특정 함수가 여러 번 반복 실행될 경우, 정해진 지연시간 동안 반복된 호출을 마지막에 한 번만 호출하도록 제어해 주는 기능을 가지고 있습니다. debounce를 사용했을 경우, 제가 가지고 있던 문제점인 너무 많은 API 호출을 제어하는데 안성맞춤이라는 생각이 들었고 debounce를 사용하여 API 호출을 제어해 주었고, 그 결과 확실히 이전에 비해 호출이 줄어든 것을 확인할 수 있었습니다.
아래는 개선 이전 코드, 이후 코드, 전체 코드입니다. 코드에 대한 설명은 이전 포스트들을 참고해 주시기 바랍니다.
(@post)
이전 코드
kakao.maps.event.addListener(map, 'dragend', function () {
const bounds = map.getBounds();
const swLatLng = bounds.getSouthWest();
const neLatLng = bounds.getNorthEast();
const newLatitude = (swLatLng.getLat() + neLatLng.getLat()) / 2;
const newLongitude = (swLatLng.getLng() + neLatLng.getLng()) / 2;
if (
locate.latitude.toFixed(3) !== newLatitude.toFixed(3) &&
locate.longitude.toFixed(3) !== newLongitude.toFixed(3)
) {
setLocate({
latitude: newLatitude,
longitude: newLongitude,
});
map.panTo(new kakao.maps.LatLng(newLatitude, newLongitude));
}
});
}, [marker, lat, lng]);
개선된 코드
kakao.maps.event.addListener(map, 'bounds_changed', function () {
debouncedUpdateLocate(map, locate, setLocate);
});
export const debouncedUpdateLocate: debounce = _.debounce((map, locate, setLocate) => {
const bounds = map.getBounds();
const swLatLng = bounds.getSouthWest();
const neLatLng = bounds.getNorthEast();
const newLatitude = (swLatLng.getLat() + neLatLng.getLat()) / 2;
const newLongitude = (swLatLng.getLng() + neLatLng.getLng()) / 2;
if (
locate.latitude.toFixed(3) !== newLatitude.toFixed(3) &&
locate.longitude.toFixed(3) !== newLongitude.toFixed(3)
) {
setLocate({
latitude: newLatitude,
longitude: newLongitude,
});
map.panTo(new kakao.maps.LatLng(newLatitude, newLongitude));
}
}, 100); // 100ms 지연 후 실행
전체 코드
export const useMapScript: MapScript = (lat, lng, draggable = true) => {
const { MenuControlldetail } = useOpen();
const marker = useRecoilValue(mapInfoAtom);
const mapRef = useRef<kakao.maps.Map | null>(null);
const [locate, setLocate] = useRecoilState(mapState);
const [isMapInitialized, setIsMapInitialized] = useRecoilState(mapInitializedAtom);
const [, setCurrentLocation] = useRecoilState(currentLocationAtom);
useEffect(() => {
if (!mapRef.current) {
const container = document.getElementById('map')!;
const options = {
center: new kakao.maps.LatLng(lat, lng),
};
const map = new kakao.maps.Map(container, options);
mapRef.current = map; // 지도 객체를 저장하여 재사용
setIsMapInitialized(true);
}
const map = mapRef.current;
draggable ? map.setDraggable(true) : map.setDraggable(false);
let currentInfoWindow: customoverlay = null;
const imageSrc = 'public/marker.png';
const imageSize = new kakao.maps.Size(35, 35);
const markerImage = new window.kakao.maps.MarkerImage(imageSrc, imageSize);
marker?.forEach((data) => {
const mark = new kakao.maps.Marker({
map: map,
position: new kakao.maps.LatLng(data.latitude, data.longitude),
title: data.placeName,
image: markerImage,
});
const content = Infowindow(data);
const overlay = new kakao.maps.CustomOverlay({
content: content,
map: map,
position: new kakao.maps.LatLng(data.latitude, data.longitude),
clickable: true,
});
kakao.maps.event.addListener(mark, 'click', function () {
if (currentInfoWindow) {
currentInfoWindow.setMap(null);
currentInfoWindow = null;
}
overlay.setMap(map);
currentInfoWindow = overlay;
setTimeout(() => {
const element = document.getElementById(data.id);
if (element) {
element.addEventListener('click', () => {
setCurrentLocation({ id: data.id });
MenuControlldetail();
});
} else {
alert('다시 시도해주세요');
}
}, 0);
});
overlay.setMap(null);
});
// 지도 클릭 시 인포윈도우 닫기
kakao.maps.event.addListener(map, 'click', function () {
if (currentInfoWindow) {
currentInfoWindow.setMap(null);
currentInfoWindow = null;
}
});
kakao.maps.event.addListener(map, 'bounds_changed', function () {
debouncedUpdateLocate(map, locate, setLocate);
});
}, [marker, lat, lng]);
useEffect(() => {
if (isMapInitialized && mapRef.current) {
const map = mapRef.current;
map.panTo(new kakao.maps.LatLng(lat, lng));
}
}, [lat, lng, isMapInitialized]);
};
'React' 카테고리의 다른 글
[React] Kakao Map API 사용하기 (4) - customoverlay 클릭 이벤트 등록하기 (1) | 2024.08.30 |
---|---|
[React] Kakao Map API 사용하기 (3) - customoverlay를 활용하여 마커 커스텀 하기 (1) | 2024.08.29 |
[React] Kakao Map API 사용하기 (2) - 마커 여러 개 등록하기 (0) | 2024.08.20 |
[React] Kakao Map API 사용하기 (1) (0) | 2024.08.05 |