이런저런거/개발 일지

KAKAO MAP

KAKAO MAP API를 사용했다.

지도에 custom overlay를 올려서 정보를 표기하고 싶었다.

 

처음 생각은 표시할 정보들을 담을 배열을 forEach 문으로 순회해서 overlay를 만들고 각각의 overlay에 click 이벤트를 달면 되겠다! 싶었다.

airQualityByCity.forEach(
      ({
        cityName,
        fineDustScale,
        ultraFineDustScale,
        fineDustGrade,
        ultraFineDustGrade,
      }) => {
        geocoder.addressSearch(cityName, (result, status) => {
          if (status === kakao.maps.services.Status.OK) {
            const latitude = Number(result[0].y);
            const longitude = Number(result[0].x);
            const backgroundColor = getDustScaleColor(fineDustScale);
            const template = `
                  <div class="dust-info-marker" id="${cityName}" data-finedustgrade="${fineDustGrade}" data-ultrafinedustgrade="${ultraFineDustGrade}" style="background-color: ${backgroundColor};" >
                    <span>${fineDustScale}/${ultraFineDustScale}</span>                  
                    <p class="city-name">${cityName}</p>                  
                  </div>`;

            const marker = new kakao.maps.CustomOverlay({
              map,
              position: new kakao.maps.LatLng(latitude, longitude),
              content: template,
            });

            if (
              !cityDustInfoMarkers.find(
                (value) => value.getPosition() === marker.getPosition()
              )
            )
              CityDustInfoMarkers.push(marker);
          }
        });
      }
    );

 

처음엔 template안에 명시적으로 onclick 이벤트를 써주려했다. 하지만 정상적으로 동작하지 않았다.

 

 두번째로 kakao map api 공식 문서를 참고했다. 근데 custom marker나 1개 overlay 혹은 info에 event를 등록하는 방법들은 있었는데 내가 원하는 대로 구현하려면 어떻게 해야할지 몰랐다. 일단 marker에 이벤트를 등록하는 문서를 보고 똑같이 사용해봤다. 

kakao.maps.event.addListener( marker, 'click', function( cluster ) {
    console.log( cluster.getCenter() );
});

근데 이것도 안된다.

 

 그래서 document.querySelector를 사용해서 dom에서 직접 element를 찾아서 이벤트를 달아주려했다. 근데 이것도 문제가 있었다. marker들을 모두 dom에 append하고 document.querySelector가 동작하여서 'click' 이벤트를 달아야 하는데 이작업들의 순서를 보장할 수 가 없었다. DOM에 marker들이 append되기전에 document.querySelector가 동작하여서 비어있는 배열만 가져왔고 이후에 marker들이 DOM에 append 되어서 이벤트가 제대로 등록되지 않는 것이었다.

 그래서 promise.all을 사용해보기로 했다. 

const result = airQualityByCity.map(
      ({
        cityName,
        fineDustScale,
        ultraFineDustScale,
        fineDustGrade,
        ultraFineDustGrade,
      }) => {
      	return new Promise((resolve, reject) => {
            geocoder.addressSearch(cityName, (result, status) => {
              if (status === kakao.maps.services.Status.OK) {
                const latitude = Number(result[0].y);
                const longitude = Number(result[0].x);
                const backgroundColor = getDustScaleColor(fineDustScale);
                const template = `
                      <div class="dust-info-marker" id="${cityName}" data-finedustgrade="${fineDustGrade}" data-ultrafinedustgrade="${ultraFineDustGrade}" style="background-color: ${backgroundColor};" >
                        <span>${fineDustScale}/${ultraFineDustScale}</span>                  
                        <p class="city-name">${cityName}</p>                  
                      </div>`;

                const marker = new kakao.maps.CustomOverlay({
                  map,
                  position: new kakao.maps.LatLng(latitude, longitude),
                  content: template,
                });

                if (
                  !cityDustInfoMarkers.find(
                    (value) => value.getPosition() === marker.getPosition()
                  )
                )
                  CityDustInfoMarkers.push(marker);
              }
            });
            resolve('성공')
            reject('실패')
          }
        })        
    );
    
Promise.all(result).then(res => {
	이벤트 리스너 달기
    })
)

이번엔 되겠지.................(됐으면 얼마나 좋았을까)

 

역시나 의도대로 동작하지 않았다.

 

그래서 위의 코드가 들어있는 useEffect문이 아니라 다른 useEffect문을 새로 만들었다. 

useEffect(() => {
    document.querySelectorAll('.dust-info-marker').forEach((city) => {
      city.addEventListener('click', () => {
        setCity(city.id);
        if (city instanceof HTMLElement) {
          city.dataset.finedustgrade
            ? setFineDustScale(+city.dataset.finedustgrade)
            : '';
          city.dataset.ultrafinedustgrade
            ? setUltraFineDustScale(+city.dataset.ultrafinedustgrade)
            : '';
        }
        onOpen();
      });
    });

    return () => {
      document.querySelectorAll('.dust-info-marker').forEach((city) => {
        city.removeEventListener('click', onOpen);
      });
    };
  }, [ currentLocation, zoomLevel]);

 음. 지도화면에 들어올때 오버레이가 바로 클릭되진 않았지만 지도를 한 번 움직이거나 zoom IN/OUT 해주면 click 이벤트가 동작했다. 하지만 원하는 동작은 아니었다.

 

그러다가 마커들을 보관하는 배열을 state를 사용하지 않고 있었다는 것을 깨달았다. 배열에 push를 사용하던 부분도 snapShot을 이용하여 state를 갱신하도록 바꾸어주었다.

// 기존
const cityDustInfoMarkers:kakao.maps.CustomOverlay[] = []

// 이후
const [cityDustInfoMarkers, setCityDustInfoMarkers] = useState<
    kakao.maps.CustomOverlay[]
  >([]);

 state가 바뀌면 재랜더링이 일어나고 자연스럽게 DOM에 append된 Element들에도 이벤트를 달 수 있겠다는 생각이 들었다.

 

바로 useEffect의 deps 바꾸러 ㄱㄱ

// 기존
[ currentLocation, zoomLevel]
// 이후
[cityDustInfoMarkers, currentLocation]

 

오 이제 좀 정상적으로 동작한다.

동영상엔 첫 화면에서 마커를 클릭하는 부분이 빠져있지만 이 역시 정상적으로 동작한다.

 

얼추 기능은 잘 동작하는거같다.

그러나 지도를 움직일 때 렉이 걸리는 현상이 있다. 불필요한 api가 계속 호출되는것 같은데 수정해보자.

marker들이 겹치는 현상도 hover 같은 이벤트로 편의성을 높이면 좋을거같다.

'이런저런거 > 개발 일지' 카테고리의 다른 글

useState, KAKAO MAP  (0) 2023.04.25