Kakao Map 전환기 - 지도 생성하기 (with Errors)

ain·2022년 12월 5일
3

Kakao API

목록 보기
1/2
post-thumbnail

본 게시물은 기존에 사용했던 Naver 지도 API에서 Kakao 지도 API로 전환하는 과정을 담았다.
사용 스택: Next.js TypeScript

서론


개인적인 이유로 Naver 지도 API를 사용하였다가 Kakao 지도 API로 전환하게 되었다. 다행히 지도 API를 처음 생성하는 것보다는 덜 오래 걸렸지만 사용법이 완전히 똑같지 않기 때문에 부가로 더 이해해야 할 것이 많았다. 지도 생성하기 뿐만 아니라 지도를 클릭해서 주소를 가져오는 것, 네이버에는 없었던 키워드로 주소 검색하기도 차례차례 구현해보며 "Kakao Map 전환기" 시리즈를 만들어 볼 예정이다.

본 게시물에서는 script를 불러오고 지도를 생성하면서 어떤 에러를 마주했는지, Naver API와 다른점이 무엇인지를 정리해보았다.


Kakao Map API 사용하기

Kakao Map API 사용하기부터 먼저 다뤄보겠다.

TypeScript와 함께 사용하기


Naver 지도 API와는 다르게 공식적으로 TypeScript와 사용할 수 있는 패키지가 없다. 그렇기 때문에 TypeScript와 같이 사용하는 방법은 두가지가 있는데, 첫번째는 전역적으로 kakao를 선언하는 것이 있고, 두번째로는 오픈 소스로 기여된 Kakao API의 타입이 정의 되어있는 패키지를 설치하여 사용하는 것이다.

  1. kakao 선언하기
    만약 TypeScript를 사용하고 있는 상태에서 script만 로드한다면 kakao가 정의되지 않았다는 에러가 발생한다. 그래서 _app.tsx에 전역적으로 any로 선언해주면 임시적으로 kakao에서 발생하는 타입에러를 막을 수 있다.
// _app.tsx
declare global {
  interface Window {
    kakao: any;
  }
  const kakao: any;
}
  1. 타입 정의 패키지 설치하기
    아래 명령어로 devDepandency에 kakao.maps.d.ts를 추가해주고, tsconfig.json의 compilerOptions.types 속성에 패키지를 추가한다.
// 설치
$ npm install kakao.maps.d.ts --save-dev
// tsconfig.json 패키지 추가
{
  ...,
  "compilerOptions": {
    ...,
    "types": [
      ...,
      "kakao.maps.d.ts"
    ]
  }
}

(패키지에 대한 더 자세한 내용은 JaeSeo Kim - kakao.maps.d.ts 참고)


script 불러오기


<Script type="text/javascript" strategy="beforeInteractive" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 JavaScript 키&libraries=services&autoload=false"></Script>
  • type : script의 타입은 text/javascript로 입력해준다.
  • strategy : 웹 성능 최적화를 도와줄 Next.js의 strategy 속성은 beforeInteractive로 값을 넣어주는데 이는 어떠한 코드보다도 먼저 script를 먼저 로드하게 도와준다. (기본값은 afterInteractive로 script가 빠르게 로딩 되지만 다른 페이지에 있는 코드가 먼저 실행될 수 있다.)
    자세한 설명은 Next.js Docs - Strategy에서 볼 수 있다.
  • // 프로토콜 : src를 보면 //로 시작하는데이 상대 프로토콜을 사용하면 사용자의 http, https 환경에 따라 자동으로 해당 프로토콜을 따라가게 된다고 설명되어있다. Kakao Maps API - 시작하기
  • librabies : src url에는 sdk 뒤에 사용할 라이브러리를 불러 올 수 있다. libraries=사용할라이브러리
  • autoload : script를 동적으로 로드하게 되면 로드가 다 끝나기도 전에 kakao api를 불러오는 코드가 먼저 실행 될 수 있기 때문에 이 autoload를 false로 만들어 자동으로 로드 되는 것을 꺼준다음, 코드 실행 부분에서 load 메서드를 사용하면 된다.

Next.js를 사용하고 있다면, _document.tsx 파일로 들어가서 <Head>태그 안에 script를 넣어준다.

// _document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';

export default function Document() {
  return (
    <Html lang="ko">
      <Head>
        <Script
          type="text/javascript"
          strategy="beforeInteractive"
          src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&libraries=services&autoload=false`}
        ></Script>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

지도 생성하기


이전에 Naver 지도 API로 작업했던 것 그대로 가져와 주었다. (⚡️"Naver 지도 사용하기"를 정리한 블로그)

  1. 지도를 담아줄 div를 만들어 준다. (TailwindCSS 사용)
    만약 지도가 생성되기 전에 로딩 애니메이션 또는 로딩 이미지 넣고 싶다면 div요소 안에 넣어준다. (로딩 애니메이션은 Flowbite - Spinner 참고)
<div id='map' className='w-96 h-96'>
  // 사용자의 위치를 가져오기 전까지는 로딩을 보여준다.
  {typeof myLocation === 'string' && <Loader />}
</div>
  1. 먼저 지도를 참조할 useRef와 사용자의 현재 위치좌표를 가져와 줄 myLocation을 가져와 준다. 이 둘은 지도를 생성할 시점을 감시하기 위해 useEffect의 의존배열에 넣어준다. 사용자의 현재 위치를 가져오는 방법은 이 블로그를 참고하여 커스텀 훅으로 만들어주었다.
// src/components/KakaoMap.tsx
import { useRef } from 'react'
import useGetCurrentLocation from '../../hooks/useGetCurrentLocation';

export const KakaoMap = () => {
  const mapRef = useRef<HTMLElement | null>(null);
  const myLocation = useGetCurrentLocation();
  
  ...
  
  useEffect(() => {
    // 지도 생성 함수
  }, [mapRef, myLocation]);
};

  1. 지도를 생성해준다. 그리고 script가 다 로드 된 후에 코드가 실행 될 수 있도록 load 메서드를 사용해준다. kakao.maps.load(callback)
export const KakaoMap = () => {
  ...
  const initMap = () => {
    // 사용자 위치를 받아왔다면 지도를 생성한다.
      if (typeof myLocation !== 'string') {
         // 지도를 표시할 요소 id
        const mapContainer = document.getElementById('map'),
          // 지도를 생성할 때의 옵션 (중심좌표, 확대레벨 등등)
          mapOption = {
            center: new kakao.maps.LatLng(myLocation.latitude, myLocation.longitude),
            level: 3,
          };
        // kakao.maps.Map(container, options)를 사용하여 지도를 생성한다.
        const map = new kakao.maps.Map(mapContainer as HTMLElement, mapOption);
        (mapRef as MutableRefObject<any>).current = map;
      }
    };
  
    useEffect(() => {
      // 스크립트가 다 load된 후 initMap을 실행하여 지도를 생성해준다.
    kakao.maps.load(() => initMap());
  }, [mapRef, myLocation]);
};


Naver 지도 API와 다른 점이 크게 없어 전환 작업이 그렇게 어렵진 않았다.

  1. TypeScript와 사용하기
    네이버 공식 문서에는 TypeScript와 같이 사용할 수 있도록 NAVER 지도 API 타입 정의 파일이 있는 반면, 카카오는 아직 없는 것으로 보인다.
    Kakao devTalk에도 올라온 질문의 답변을 보면 any를 사용하라고 한다.

  1. script 태그 load하기
    script태그에 발급받은 JavaScript key를 넣고 사용할 library를 넣는 것은 비슷하지만, Naver에서는 스크립트 로드가 끝나면 실행할 callback함수를 바로 적어주는 것과 다르게 Kakao는 autoload를 끄는 방식이다.
    그래서 kakao API를 사용할 때 autoload를 false로 설정했다면 지도를 생성하는 함수를 kakao.maps.load 메서드로 실행해주어야 한다.

Naver script:

https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.MAP_KEY}&submodules=geocoder&callback=initMap

Kakao script

https://dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.KAKAO_API_KEY}&libraries=services&autoload=false

  1. 지도 생성 parameters
    Naver API로 지도를 생성할때, 첫번째 parameter에 바로 문자열 형식으로 id를 주면 되지만, Kakao API를 사용한다면, DOM에 접근하여 id가 'map'인 요소를 찾은 후, parameter로 넘겨야 한다.
// naver
const map = new naver.maps.Map('map');
// kakao
const map = new kakao.maps.Map(document.getElementById('map'), options);

또한, kakao는 map을 꾸며주는 options가 없으면 에러가 발생한다.


결과 화면

성공적이라면 사용자의 현재 위치를 지도로 보여준다.

에러 모음

1. "kakao is not defined" / "kakao.maps.LatLng is not defined" / "Failed to execute 'write' on 'Document'"

script태그가 다 로드 되기 전에 kakao API를 사용하는 코드가 먼저 실행 됐기 때문에 발생하는 에러이거나, 타입스크립트를 기반으로 하는 프로젝트라면 타입이 지정되지 않았기 때문이다.
해볼만한 시도 1:
script태그 src 제일 뒤에 autoload=false 추가하기.
해볼만한 시도 2:
script태그에 strategy 속성을 추가하여 beforeInteractive로 설정해준다.
해볼만한 시도 3:
전역에 kakao 선언하기

declare global {
  interface Window {
    kakao: any;
  }
  const kakao: any;
}

2. 빈 화면

만약 useEffect의 의존배열이 빈 배열이라면 지도가 렌더링 되지 않으므로 사용자 위치를 가져왔을 때 생성된 지도를 가져올 수 있도록 참조한 지도 ref와 사용자 위치를 의존배열에 넣어준다.

useEffect(() => {
    kakao.maps.load(() => initMap());
  }, [mapRef, myLocation]);

참고

profile
프론트엔드 개발 연습장 ✏️ 📗

0개의 댓글