[React_typescript]kakao 지도 api 연동하기 (키워드 검색 기능 추가)
새로운 프로젝트를 하는 중 kakao 지도를 연동하고 검색하는 기능을 구현하게 되었다.
만들고 보니 간단했던 것 같은 착각(?)이 들지만 한참 해멨던 기능이기에 정리해보려고 한다.
구현영상
선행 과정
먼저 지도를 원하는 페이지와 연동하기 위해서는 카카오 developers에 내 어플리케이션을 등록하고 api키를 받아오는 과정이 먼저 선행되어야 한다.
그 과정은 어렵지 않고 많이 나와있으니 따로 기록하지 않고 넘어가려한다.
등록하는 과정이 끝나고 나오는 4가지 종류의 api key중 javascript api key를 이용하면 된다.
사용한 라이브러리
npm install react-kakao-maps-sdk
카카오 map과 관련되어 위 라이브러리를 이용했다.
코드 적용
이제 가지고 온 api key를 html 파일에 입력한다
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
<div id="root"></div>
//api키는 env 파일에 적어둠
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=%REACT_APP_KAKAO_API_KEY%&libraries=services,clusterer,drawing"></script>
</body>
</html>
여기서 주의해야할 점은 당연히 appkey가 노출되지 않게 하는 것과 script src 마지막에 있는 libraries들을 적어주는 것이다.
단순히 지도를 띄우는 것만 한다면 라이브러리 기능은 크게 필요없지만 나는 검색기능과 나중에 데이터를 기반으로 마커를 찍어주는 기능까지 구현할 예정이기 때문에 해당하는 라이브러리도 함께 적어줬다.
추가로 typescript를 사용하고 있다면 tsconfig.json 파일에 아래와 같이 types를 적어줘야 한다.
//tsconfig.json
{
"compilerOptions": {
...
"types": [
"kakao.maps.d.ts"
]
}
타입스크립트 핸드북을 보니 d.ts는 타입스크립트의 타입추론을 돕는 파일이라고 나와있었다.
해당 파일에 kakao.map.d.ts를 추가함으로써 라이브러리를 ts 로 사용하기 위한 것 같다.
이 과정을 거치지 않는다면 kakao이름을 찾을 수 없다는 오류가 뜨게 된다.
이 오류를 해결하기 위해 kakao 앞에 window를 붙여 window.kakao 로 해결하는 방법도 있다고 봤지만 타입 정의 파일을 정의해줌으로써 타입스크립트에 더 정확한 방식 같아 위 방법을 사용했다.
주요기능이 들어있는 map.tsx파일이다.
아래의 대부분의 코드는 kakaomap 공식문서에 있는 코드와 비슷하다
//map.tsx
import React, { useEffect, useState } from 'react';
import * as S from "./styled";
const MapContainer = () => {
const [value, setValue] = useState<string>("")
const [searchvalue, setSearchValue] = useState<string>("")
useEffect(() => {
// 마커를 클릭하면 장소명을 표출할 인포윈도우 입니다
let infowindow = new kakao.maps.InfoWindow({zIndex:1});
let container = document.getElementById('map') as HTMLElement; //지도를 담을 영역의 DOM 레퍼런스
let options = { //지도를 생성할 때 필요한 기본 옵션
center: new kakao.maps.LatLng(33.450701, 126.570667), //지도의 중심좌표.
level: 4 //지도의 레벨(확대, 축소 정도)
};
let map = new kakao.maps.Map(container, options); //지도 생성 및 객체 리턴
if (!map) return console.log("fine")
if (searchvalue === "") return console.log("값이 없음")
const ps = new kakao.maps.services.Places()
ps.keywordSearch(searchvalue, (data : any, status: any, _pagination: any) => {
if (status === kakao.maps.services.Status.OK) {
// 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해
// LatLngBounds 객체에 좌표를 추가합니다
const bounds = new kakao.maps.LatLngBounds()
for (var i = 0; i < data.length; i++) {
displayMarker(data[i])
bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x))
}
// 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
map!.setBounds(bounds)
}
})
// 지도에 마커를 표시하는 함수입니다
function displayMarker(place : any) {
// 마커를 생성하고 지도에 표시합니다
let marker = new kakao.maps.Marker({
map: map,
position: new kakao.maps.LatLng(place.y, place.x)
});
// 마커에 클릭이벤트를 등록합니다
kakao.maps.event.addListener(marker, 'click', function() {
// 마커를 클릭하면 장소명이 인포윈도우에 표출됩니다
infowindow.setContent('<div style="padding:5px;font-size:12px;">' + place.place_name + '</div>');
infowindow.open(map, marker);
});
}
}, [searchvalue])
//검색 결과 state에 전달
const searchResult = (e:any) => {
setValue(e.target.value)
}
const onKeyPressEnter = (e:any) => {
if(e.key === "Enter") {
search();
setValue('')
}
}
const search = () => {
setSearchValue(value)
}
return (
<div>
<S.Map id="map" />
<S.SearchBar>
<input
className='Search'
type="text"
placeholder='원하는 장소를 입력해주세요'
onChange={searchResult}
onKeyPress={onKeyPressEnter}
></input>
<BsSearch
style={{"marginLeft":"10px"}}
onClick={search}
/>
</S.SearchBar>
</div>
);
}
export default MapContainer;
지도를 그리기만 하는 것은 간단했다.
공식문서에 나와있는대로 옵션을 지정해주고 kakao.map.Map class를 이용해 지도를 생성하면 된다.
이 때, 크기 지정해주는 과정은 잊지 말아야 한다.
하지만 검색 기능을 추가하려고 하니 동기 비동기 오류가 자꾸 나를 괴롭혔다..
그래서 추가하게 된 코드가 useEffect 이다.
처음에는 지도를 그리는 부분 밑에다가 useEffect를 넣어봤는데, 처음 랜더링 될 때는 되다가 새로고침하면 안되는 현상이 생겼고
이 것을 해결하기 위해 지도 생성부터 마커표시까지의 코드를 useEffect에다가 넣어주며 두번째 인자에 검색 결과가 바뀔 때마다 랜더링 되도록 조건을 줬다.
그 뒤 하단에 검색 input창을 만들어서 해당 값을 변수로 전달해주는 것으로 기능을 완성했다.
+ 하고싶은 기능이 웹(or 앱) 상에서 지도가 움직이는 것이 아니라 카카오map으로 연결하는 것이라면 아래 공식문서를 보고 URL로 넘어가면 된다.
https://apis.map.kakao.com/web/guide/#routeurl
api 공식문서만 봤을 때는 간단해보였지만
처음 사용해보는 typescript를 이용해서 하려니 type error가 어마무시하게 났다..
그냥 type만 지정해주면 되는거 아니야? 생각했다가 아주 큰 코 다친 이번 경험이였다.
지금은 모든 type을 any로 적어뒀지만, any를 쓰는건 최대한 지양해야 한다고 본적이 있다.
typescript에 대해서 조금 더 공부한 뒤 해당 부분 리펙토링해야겠다는 생각이 들었다.
이번 기능을 구현하면서 react 사용하면서 많이 사용하지 않아 거의 기억에서 없어진 class 개념에 대해서도 다시 한 번 공부할 수 있는 시간이였고 동기/비동기 문제로 한참을 씨름하면서 useEffect 에 대해서도 잘 모르고 있구나 라는 것을 깨달았다.
참고
https://apis.map.kakao.com/web/sample/keywordBasic/
https://velog.io/@yeum0523/React%EB%A1%9C-Kakao-Map-%EB%9D%84%EC%9A%B0%EA%B8%B0