221029 마커 클릭 이벤트 구현 1차 실패 & 지도 코드 리팩터링

샨티(shanti)·2022년 10월 29일
0

하루를 마무리 하기 전, 오늘 있었던 일들을 잔잔히 되짚어봅니다.
성공과 실패의 모든 요소에서 '배울 점'을 찾아내어 기록하고,
더 성장하는 내일의 나를 위해 'action plan'을 세웁니다.

오늘은 약간 버겁다는 생각이 들었다.
카카오 지도 API에 나와있는 예시들을 활용할 수는 있겠는데 많은 경험자들이 입모아 얘기하듯 결국에는 내가 구현하고자 하는 코드, 결과물에 따라 해당 내용들을 수정해야 하기 때문에 결국엔 여러가지 실험과 삽질이 많이 필요하다.

오늘은 거의 종일 마커 클릭 이벤트를 구현해보고자 노력했지만 결국엔 실패하고 차선책으로 마커에 마우스를 올려놓았을 때 인포윈도우가 뜨는 예시를 정상적으로 작동하게 만드는 데 그치게 되었다.

우선 오전/오후 시간에는 다수의 마커를 지도 위에 띄우는 작업을 했는데, 처음 태스크에는 30개를 띄워보고자 했지만 빠르게 mvp를 만들어 내는 것이 중요할 것 같아 하드코딩으로 넣는 양을 5개로 대폭 줄였다.

아래는 임의로 5개의 장소에 대해 구글 맵에서 위도, 경도를 검색하여 정보를 넣어주고 띄워본 마커 모습

사실 여기도 우여곡절이 없지는 않았다.
카카오맵의 예시들을 보면 변수를 대체로 var로 선언하고 있는데 이 부분을 바꿔주는 데도 조금 시간이 걸렸고...
내가 구현해 놓은 원래의 코드 상태에 맞추어 조정해야 할 것들이 있다보니 아주 원활하게 작업을 진행하지는 못했다.

그리고 가장 난관이었던 것은 사실, 추후에 백엔드에서 장소 정보를 가져올 것을 고려하여 custom hook, store, service를 생성하여 fetchAllPositions() 라는 메서드의 기능을 정의하고, 이를 통해 장소 정보를 포함한 positions라는 배열을 가져오는 로직을 만드는 부분이었다.

로직은 분명히 틀린 것이 없어보였고 일부러 틀리도록 구현한 Test의 결과에서도 mapApiService의 fetchAllPositions()의 반환값이 임의로 넣은 positions 배열과 동일했다.

대체 외않되?
배열은 불러오는데 useEffect 안에서 fetch를 한 값이 제대로 불러와지지 않았고 콘솔에서는 '너 지금 hook이 지켜야 할 룰을 무시한것 같아!!' 라고 외치고 있었다.

멘붕이었다.
참고할 수 있는 자료나 블로그 글은 넘쳐나는 것 같지만 실상 내가 구현한 코드와 딱 떨어지는 것은 거의 없어보였고, 외부 api를 갖다 쓰는 것이다보니 내부 로직을 속속들이 알지 못하는 상황에서 그들이 제시하는 룰에 코드 구현을 맞추어나가는 부분이 쉽지 않았다.

저-엉말 오랜 씨름 끝에. 저녁을 먹고 와서도 꽤 오랜 시간 싸우고 나서야 아주 마음에 드는 코드는 아니지만 그래도 '돌아가는' 상태를 만들어냈다.

이 과정에서 새롭게 알게된 점은, (1) useEffect의 호출 순서가 하위 컴포넌트 -> 상위 컴포넌트 순서대로 진행된다는 점, (2) 특히 fetch~ 와 같은 기능을 사용할 땐 반드시 '새로고침(refresh)' 경우를 고려해야 한다는 점, (3) useEffect의 순서를 지켜주기 위해서는 루프나 조건문 안에 useEffect를 사용해서는 안된다는 점(단, useEffect 안에서는 조건문 사용하는 것 오케이!) 정도가 있다.

먼저 useEffect 호출 순서가 아주 당연히 top-down일거라 생각해서 상위 Page에서 useEffect를 사용하고, 그 하위 컴포넌트에서도 useEffect를 사용해서 원하는 방향으로 마커를 뿌려주는 코드를 구현했는데 요상하게 새로고침만 했다 하면 빈 배열이 전달되면서 마커가 출력되지 않았다.

애초에 useEffect를 function 컴포넌트 안에서 사용하라는 에러 메시지에 시달린터라 맥북 화면에 주먹질을 하고싶었지만...
도대체 뭔 일인가 싶어서 각 useEffect 안에 콘솔로그를 넣어 순서를 확인해보았다.

그랬더니 생각지도 못한 일이... 응...?
당연히 Page 컴포넌트 안에 있는 useEffect가 먼저 뜰 줄 알았는데 그 하위 컴포넌트인 Map 컴포넌트 속 useEffect가 먼저 나타나는게 아닌가?
물론, 새로고침을 하면 store 안에 있는 positions 배열이 초기화되면서 그 배열이 전달되어 마커가 뜨지 않는 오류가 있는데 그와는 별개로 useEffect의 호출 순서 때문에 발생하는 문제도 있었다.

결국 위에서 언급한 두 가지 문제 모두 해결될 기미가 없어보여 우선 useEffect의 순서를 오롯이 지켜주면서 한 컴포넌트 안에서 두개의 useEffect가 실행되는 방향으로 구현했다.

그 과정에서 기존에 구현해두었던 카카오 맵의 파일이나 폴더 구조도 조금 간결하게 만들었다.

클릭 이벤트는...
홈페이지 예시를 말 그대로 복붙했으나... ㅎㅎㅎㅎㅎ 안됐다.

여러개의 마커를 띄워놓은 상태인데 그 코드를 또다시 뒤엎는 새로운 코드로 덧붙여야 하는 상황이더라.

뒤엎더라도 기능이 구현되면 좋은데 우선 되질 않았다.
그래도 이렇게 가만히 있을 순 없고 이벤트와 관련된 무엇이라도 한번은 해 봐야 그 다음 더 높은 단계인 클릭 이벤트를 해볼 수 있을 것 같아 예시의 인포윈도우를 띄우는 코드를 내 코드에 맞게 수정하여 적용해보았다.

eslint를 사용하고 있다보니 기존 예시를 그대-로 복붙해오면 빨간줄이 난리난리. 순서가 고려되지 않은 것 같은 코드도 있어서 그런 부분들은 가져와서 좀 수정했다.

장소 중 덕수궁과 국립중앙박물관에 마우스 오버를 하면 뜨는 인포 창.

그리고 어제와 비교해서 추가 & 리팩터링된 카카오맵 지도 코드.
아래와 같이 바뀌었다.

useEffect 안에 들어간 카카오 지도 관련 내용들은 나중에 별도의 module로 다시 빼주는 게 좋을 것 같다.

원래는 별도로 있었는데 오늘 데이터 불러오는 로직 만들면서 너무 학을 떼서.. 우선 한군데다가 다시 때려넣고 원복해놓은 상황.

// Map.jsx

import { useEffect } from 'react';

import useMapStore from '../hooks/useMapStore';

const { kakao } = window;

export default function Map() {
  const mapStore = useMapStore();

  useEffect(() => {
    mapStore.fetchAllPositions();
  }, []);

  // 인포윈도우를 표시하는 클로저를 만드는 함수입니다
  function makeOverListener(map, marker, infowindow) {
    return () => {
      infowindow.open(map, marker);
    };
  }

  // 인포윈도우를 닫는 클로저를 만드는 함수입니다
  function makeOutListener(infowindow) {
    return () => {
      infowindow.close();
    };
  }

  useEffect(() => {
    const container = document.getElementById('myMap');
    const options = {
      center: new kakao.maps.LatLng(37.565804, 126.975146),
      level: 5,
    };

    const map = new kakao.maps.Map(container, options);

    const { positions } = mapStore;

    positions.forEach((value) => {
      const marker = new kakao.maps.Marker({
        map,
        position: new kakao.maps.LatLng(value.latitude, value.longitude),
      });
      const infowindow = new kakao.maps.InfoWindow({
        content: value.title,
      });

      kakao.maps.event.addListener(
        marker,
        'mouseover',
        makeOverListener(map, marker, infowindow),
      );

      kakao.maps.event.addListener(
        marker,
        'mouseout',
        makeOutListener(infowindow),
      );
    });
  }, []);

  return (
    <div
      id="myMap"
      style={{
        width: '500px',
        height: '500px',
      }}
    />
  );
}

이 맵 컴포넌트를 MapPage.jsx안에서 렌더링 해주었다.

뭔가 강의의 순서를 따라가는 것이 아니고, 또 이미 주어진 외부 API가 있음에도 불구하고 내 상황에 맞춰서 변형하고 또 활용하는 것이 쉽지 않다는 점을 깨닫는 시간이었다.

아마 현업에서도 이런 일들이 비일비재 할텐데...
비슷한 예시가 있다면 적극적으로 따라해보고, 또 구현하기 전에 다양한 방법으로 고민하고 설계를 먼저하면서 작업을 해야겠다는 생각이 들었다.

그리고 무엇보다도 아주 작은 단위부터 시작하는 것의 중요함을 매번 느낀다.

오늘 30개의 데이터를 고민하면서 '스크래핑' 기능을 공부해야 하나? 잠시 딴길로 샐 뻔 했는데.
어떤 기능을 먼저 구현할지 우선순위를 떠올린 다음에 작업의 단위를 작게 만들고나니 어떤 부분이 어려운지도 더 명료하게 보였고 생각지 않게 해결이 된 부분도 있었다.

아직 프론트에서만 작업중이기에 이래도 되나? 싶은 마음도 들지만 ㅎㅎ 우선 내일은 아주 간단한 결과물을 낼 수 있는 클릭 이벤트가 정상적으로 구현이 좀 되었으면 좋겠다.

화이팅!!

profile
가벼운 사진, 그렇지 못한 글

0개의 댓글