신입 개발자에서 시니어 개발자가 되기까지

[메인 프로젝트] kakao map api 리팩토링하기(feat. 함수 추출하기) 본문

코드스테이츠

[메인 프로젝트] kakao map api 리팩토링하기(feat. 함수 추출하기)

Jin.K 2023. 3. 4. 23:43

마커 클러스터링을 브로깅하면서 setMarkerCluster 함수가 너무 길어서 리팩토링을 하기로 마음 먹었다.
리팩토링 방법은 진우님께서 추천해주신 리팩토링 6장에 있는 함수 추출하기다.

지도 생성함수 추출하기

여러 함수에서 카카오 지도를 생성하고 있었기 때문에 반복되는 코드를 함수로 만들어서 재사용 했다.

const generateKakaoMap = (center: getMapAndMarkerPropsType['center']) => {
  let mapContainer =
      document.getElementById('map') || document.createElement('div'), // 지도를 표시할 div
    mapOption = {
      center: new kakao.maps.LatLng(center.lat, center.lng), // 지도의 중심좌표
      level: 5, // 지도의 확대 레벨
    };
  return new kakao.maps.Map(mapContainer, mapOption);
};

이렇게 추출한 후

const map = generateKakaoMap(coords);

이렇게 썼다.

커스텀 오버레이 생성 코드 함수 추출하기

커스텀 오버레이를 생성하고, 이벤트를 바인딩 해주는 코드가 상당히 길고 따로 추출할 수 있을 것 같아서 이렇게 추출했다.
다만, 여기 안에 있는 이벤트를 바인딩하는 함수는 추출하기 어렵다.
isOverlayOpen이라는 변수를 캡슐화해서 사용하기 때문이다. 각각의 마커들에 이벤트를 바인딩 해주고, 이 변수를 함수 내에서 참조하고 있다.
마커를 하나 클릭할 때마다 true / false로 변경하고 true면 커스텀 오버레이를 보여주는 방식이기 때문에, 각각의 마커에는 각각의 isOverlayOpen 이라는 상태가 존재해야 한다.
그런 맥락에서 openAllOverlayButton은 marker처럼 여러 개가 있는 게 아니고 지도위에 있는 모든 게시물을 볼 수 있게 만들어주는 버튼인데 이 버튼을 굳이 반복문을 돌리면서 event를 바인딩 해주는 것도 위에서 기술한 이유 때문이다.

const setCustomOverlay = (
  sharingLists: kakaoMapItemType[],
  markers: any[],
  map: any
) => {
  for (let i = 0; i < sharingLists?.length; i++) {
    const openAllOverlayButton = document.getElementById('openAllOverlay');

... 중략
 let isOverlayOpen = false;
 ... 중략
   kakao.maps.event.addListener(sharingItemMarker, 'click', function () {
      if (!isOverlayOpen) {
        customOverlay.setMap(map);
        isOverlayOpen = true;
      } else if (isOverlayOpen) {
        customOverlay.setMap(null);
        isOverlayOpen = false;
      }
    });
    openAllOverlayButton?.addEventListener('click', function () {
      if (!isOverlayOpen) {
        customOverlay.setMap(map);
        isOverlayOpen = true;
      } else if (isOverlayOpen) {
        customOverlay.setMap(null);
        isOverlayOpen = false;
      }
    });
};

이 함수를 setMarkerCluster 함수 내에서 호출한다.

  setCustomOverlay(sharingLists, markers, map);

리팩토링 후

export const setMarkerCluster = async (
  coords: getMapAndMarkerPropsType['center'],
  sharingLists: kakaoMapItemType[],
  setMapCenter: getMapAndMarkerPropsType['setTargetCoord'],
  setIsMapLoading: React.Dispatch<React.SetStateAction<boolean>>
) => {
  const map = generateKakaoMap(coords);
  let marker = new kakao.maps.Marker({ position: map.getCenter() }); // 클릭한 위치를 표시할 마커입니다
  marker.setMap(map);
  marker.setTitle('지도 중심');
  // 마커 클러스터러를 생성합니다
  const clusterer = new kakao.maps.MarkerClusterer({
    map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
    averageCenter: true, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
    minLevel: 5, // 클러스터 할 최소 지도 레벨
  });
  const zoomControl = new kakao.maps.ZoomControl();
  map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);
  //Kakao maps에서 제공하는 marker를 담을 배열
  let markers: any[] = [];
  let mapLevel: number;
  kakao.maps.event.addListener(map, 'dragend', function () {
    let latlng: any = map.getCenter();
    // latlng가 any가 아닐 때 Ma와 La 필드가 latlng에 존재하지 않는다고 오류뜸.
    const mapCenter = { lat: latlng.Ma, lng: latlng.La };
    marker.setPosition(new kakao.maps.LatLng(mapCenter.lat, mapCenter.lng));
    setDefaultCoordsAndAddress(mapCenter, (result, status) => {
      if (status === kakao.maps.services.Status.OK) {
        let detailAddr = !!result[0].address.address_name
          ? result[0].address.address_name
          : result[0].road_address.address_name;
        setMapCenter((prev: any) => {
          return { ...prev, ...mapCenter, address: detailAddr };
        });
      }
    });
  });
  setCustomOverlay(sharingLists, markers, map);

  clusterer.addMarkers(markers);
  setIsMapLoading(false);
};