리액트에서 맵을 전역으로 사용하고 싶을 때

맨 처음 진입점에서 카카오 맵을 한 번 로딩하고 이후에는 새로 불러오지 않고 재활용하고자 하는데요. (그렇지 않으면맵 위에 맵이 자꾸 겹쳐져서 확대/축소하면 이전의 맵이 보이더라구요)

  useEffect(() => {
    if (myKakaoMap === null) {
      let container = document.getElementById("KakaoMapContainer");
      let options = {
        center: new kakao.maps.LatLng(37.508020, 127.062835),
        level: 3
      };
      // eslint-disable-next-line
      let map = new kakao.maps.Map(container, options);
      console.log('loading kakaomap');
      setKakaoMap(map);
    } else {
      // ?
    }
  }, []);

이런식으로 맵 객체 없으면 전역(Context, Redux)에 저장하고 있는데 이후에 꺼내올때는 어떤식으로 꺼내와야 할까요?

좀 더 코드레벨로 얘기하자면,
새로운 맵을 생성하지 않고 기존의 맵을 원하는 HTML element에 넣으려면 어떻게 하면 될까요?

아니면 이보다 더 좋은 방안이 있을까요?

React Hook으로 지도 사용하기

정적 로딩 방식(script src)

index.html를 보면 최상단 view에 스크립트 태그를 선언한 것이 보입니다.
직접 발급받은 앱키를 사용해 보시려면
하단의 테스트 결과 창에 보이는 URL을 Kakao developers console에 도메인 등록하셔야 합니다.


동적 로딩 방식 (autoload=false)

KakaoMap.js 해당 파일에서 동적 스크립트 로딩 로직을 보실 수 있습니다.
다른 앱키를 사용하려면 정적 로딩 방식과 같은 방식으로 변경해 주세요.


  • 위에 제공된 예제들은 React에서 카카오 지도를 띄우기 위한 방법에 대한 레퍼런스 목적으로 제작 되었습니다.
  • 정상동작하지 않거나 코드 로직상 문제가 있다면 @doji.doo 에게 제보 부탁드립니다.
  • 작성된 내용 외에 개발자 분들의 비지니스 로직을 위한 코드는 제공해 드릴 계획이 없으니 참고 부탁드립니다.

맵을 처음 로딩하는 방법을 알려주신 것 같은데, 이미 map은 로딩한 상태입니다.
그 뒤에 새로 맵을 로딩하지 않고 이미 로딩한 맵을 다시 원하는 dom element에 그리는 방법이 궁금합니다.

카카오 지도를 그리는 컴포넌트가 조건에 의해 렌더링 되지 않으면 다시 그리는게 맞습니다.

단순히 스타일 상으로 display: hidden을 한 경우라면 모르겠지만…(이 경우 어떻게든 재사용 가능하겠죠)

지도 API의 맵 객체는 DOM 엘리먼트를 직접 받아서 뷰를 구성하고 있으므로 (Map 생성자 첫 번째 인자)
생성시 container가 되는 DOM과 밀접한 관련이 있다는 것을 의미하고 있습니다.
해당 DOM 엘리먼트가 화면에 렌더링 되지 않았다면 만들어진 Map 인스턴스는 정상동작 하지 않습니다.
즉, 이미 특정 container를 인자로 넘겨서 생성된 지도를 다른 container로 옮기는 방식의 재사용은 불가능합니다.

지도가 두 개 뜬다는건
지도를 초기화 하는 Effect가 로직상 어떠한 이유로(의존성 참조 값 변화, 로직 오류)
Mount 이 후에 두 번 호출 되었다는 의미인데,
그 부분을 한 번만 호출되도록 제어해 주시면 될 것 같은데요.

네 이리저리 둘러봐도 다시 그려주는 방법이 없는것 같아서 다른 방법으로 하고자 하는 기능을 구현해보는 중이었습니다.ㅠ

궁극적으로 하려고 하는건 다른 url을 갔다가 돌아와도 보고 있던 지도를 결과(마커와 반경을 표시하는 원) 그대로 보여주고자 하는 것인데요.

(지금 카카오맵 같은 경우 강남역 맛집을 검색한 뒤에 주소 탭으로 갔다가 다시 전체 탭을 누르면 찍혀있던 마커들과 정보들이 그대로 다시 보이니까 제가 원하는 방식대로 동작을 하고 있네요. 카카오맵은 URL이동이 없지만요)

이렇게 지도의 상태를 유지시키려면 지도의 중심좌표, 클러스터등을 어딘가에 맵 정보로 저장해두었다가 새로운 맵을 그리고 저장해 둔 맵 정보를 불러오는 방법밖에 없을까요?

map.kakao.com에서 구현된 방식은…
지도를 하나만 생성해서 쓰고 있습니다. 그리고 탭별로 참조할 데이터를 컨트롤러에서 따로 관리하고 있습니다.
그래서 해당 탭을 클릭하면 이 데이터를 기준으로
기존에 그려진 모든 오버레이들은 지우고 다시 그리거나
일부만 지우고 추가하여 그리는 거죠.

리액스의 흐름으로 다시 써 보면
일종의 상태 변수를 구조화 하여 지도 컴포넌트에 전달해 주는 것이라고 보면 됩니다.
지도는 그대로 하나만 생성해서 두고
상위 컴포넌트에서 prop으로 넣어주는 marker나 도형들 정보를 받아서 그려주는 방식으로 data flow가 진행됩니다.

제가 드린 codesandbox 예제를 보시면, (좀 바꿨습니다.)
useRef() 를 써서
props가 변할 때마다 Effect에서 이 전에 만들어둔 마커들을 돌면서 지워주는 로직이 있습니다.
마커의 좌표로 쓸 위경도 데이터 배열을 버튼 클릭에 따라 각기 달리 넘겨주는데요.
해당 버튼을 번갈아가며 클릭할 때마다
기존의 마커들을 지우고 새로운 데이터를 지도에 반영하는 것을 볼 수 있습니다.

이 흐름을 기준으로 입코딩으로 구현해 본다면

  1. props가 변경될 때 의존성이 걸린 Effect가 실행
  2. ref 변수에 저장되어 있는 기존 오버레이는 지우고
  3. props로 받은 데이터를 기준으로 새로운 오버레이들을 만듬
  4. 여기서 만들어진 새로운 오버레이는 ref 변수에 할당

위와 같이 진행되면 다른 데이터가 들어와도 지도를 다시 만든 것 처럼 사용할 수 있습니다.

1개의 좋아요

useRef가 DOM element 가리키는 역할만 한다고 생각했는데 컴포넌트가 리 렌더링 될 때도 state를 유지하며 저렇게사용이 될수가 있는건 처음 알았네요. 예시 감사합니다.

마지막으로 조금만 더 여쭤보겠습니다.
카카오맵에서 검색 <-> 지하철 탭 이동간에는 지도가 visible/invisible 해지는데
이 때 구현하는 방법(혹은 추천하시는 방법은) 예시에 보여주신 것처럼 css의 visible 속성을 바꾸는것일까요?

맞습니다.
다만 스타일변경으로 지도를 감싸는 container의 사이즈가 변경되고 난 뒤에
다시 원래 사이즈로 복구시키려면
스타일 변경 전 기존의 지도 중심 좌표를 기억했다가

useEffect(() => {
    // (대충 container가 다시 show 되는 코드)
    map.relayout();
    map.setCenter(center);
}, [visible]);

이렇게 하면 되지 않을까요? 이건 제가 안해봐서 모르겠는데
오늘은 이만 퇴근해야 하니… 다음주에 해 보고 예제를 업데이트 해 보겠습니다. 즐건 주말 되세요.

감사합니다 좋은 주말 되세요 :smile:

스타일을 통한 show/hide는 아래와 같은 코드로 처리했습니다.

useEffect(() => {
  if (kakaoMap === null) {
    return;
  }

  // save center position
  const center = kakaoMap.getCenter();

  // change viewport size
  const [width, height] = size;
  container.current.style.width = `${width}px`;
  container.current.style.height = `${height}px`;

  // relayout and...
  kakaoMap.relayout();
  // restore
  kakaoMap.setCenter(center);
}, [kakaoMap, size]);

이 전에 공유해 드린 code sandbox 링크에 전체 코드가 있습니다.
이렇게 할 수도 있다 정도로만 참고해 주세요.

제가 React hooks으로 어플리케이션 개발을 해 본적이 없어서
공식 문서와 테크 문서를 참고하면서 예제를 만들다 보니
최초 공유해 드린 코드와 다르게 여기저기 수정된 부분이 있습니다.
(marker 관련 ref 삭제, container DOM을 ref로 대체 등등)
이 점 양해 부탁드립니다.

2개의 좋아요

@lea.ju

도움 많이됐습니다. 감사합니다 :slight_smile: