회사에서 홈페이지 리뉴얼을 진행하면서 카카오맵을 사용해야 했다.
카카오 Maps API 공식문서를 보고 구현하고자 했는데, 공식문서의 내용을 현재 개발환경에 맞게 적절하게 코드를 변환하지 못해 어려움을 겪었다.
추후에 추가로 포스팅할 예정이지만 구글 리캡챠 역시 비슷한 문제를 겪었기에 관련 내용을 정리하며 공식문서의 코드들을 이해하고 개발환경에 적합하게 변화시킬 수 있는 실력을 함양하고자 내용을 포스팅하려 한다.
사용 기술스택
- React (Next.js)
- TypeScript
공식문서 가이드
우선 공식문서 가이드에 따라 작성된 코드는 아래와 같다.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Kakao 지도 시작하기</title>
</head>
<body>
// 1. 지도를 담을 div 생성
<div id="map" style="width:500px;height:400px;"></div>
// 2. 지도를 그려줄 Javascript API 불러오기
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 APP KEY를 넣으시면 됩니다."></script>
// 3. 지도를 띄우는 코드 작성
<script>
var container = document.getElementById('map');
var options = {
center: new kakao.maps.LatLng(33.450701, 126.570667),
level: 3
};
var map = new kakao.maps.Map(container, options);
</script>
</body>
</html>
내가 구현한 코드
아래구현된 코드 로직은 다음과 같다.
Footer 컴포넌트에서 주소를 클릭하면 카카오맵이 모달창으로 출력된다.
주소를 클릭하는 이벤트가 발생하면 mapViewState 값이 변화하고 이 값이 true이면 모달창이 출력된다.
카카오맵 모달창은 지도 바깥을 클릭하거나 esc 키를 입력하면 mapViewState가 false 값으로 업데이트 되고 모달창이 사라진다.
1. 지도를 담을 div 생성
function Map() {
...
return (
<div
className={mapViewState ? styles.mapWrapper : styles.hideMap}
onClick={callback}
aria-hidden='true'
>
<div id='map' className={styles.map}>
<h2>This is map</h2>
</div>
</div>
);
}
Footer 컴포넌트를 클릭하면 카카오맵을 띄울 모달창을 Map 컴포넌트로 구현하였으며 부모컴포넌트에서 mapViewState 값에 따라 모달창이 사라지고 나타나도록 css를 스타일링 하였다.
카카오 맵이 출력된 이후 클릭 이벤트가 발생하면 mapViewState 값이 변화해야하는데, 이를 실행해줄 함수가 부모컴포넌트에 있으므로 자식 컴포넌트에서 부모컴포넌트의 함수를 실행하기 위해 props로 callback 함수를 전달하고 onClick 이벤트가 발생하면 해당 callback 함수가 실행되도록 하였다.
2. 지도를 그려줄 API 가져오기
function Map() {
...
useEffect(() => {
const mapScript = document.createElement('script');
mapScript.async = true;
mapScript.src = `//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAOMAP_APPKEY}&autoload=false`;
document.head.appendChild(mapScript);
}, []);
return(
...
);
};
appkey가 노출되는 것은 좋지 않으므로 따로 .env.local 파일을 생성하여 저장하고 process.env로 불러와서 사용하도록 했고
페이지가 랜더링될 때 script 요소를 생성해서 head 태그에 추가되면서 api가 가져오도록 했다.
3. 지도를 띄우는 코드 작성
function Map({
latitude,
longitude,
mapViewState,
callback,
}: MapProps) {
useEffect(() => {
const mapScript = document.createElement('script');
...
document.head.appendChild(mapScript);
const onLoadKakaoMap = () => {
window.kakao.maps.load(() => {
const container = document.getElementById('map');
const options = {
center: new window.kakao.maps.LatLng(latitude, longitude),
};
const map = new window.kakao.maps.Map(container, options);
const markerPosition = new window.kakao.maps.LatLng(
latitude,
longitude
);
const marker = new window.kakao.maps.Marker({
position: markerPosition,
});
marker.setMap(map);
});
};
mapScript.addEventListener('load', onLoadKakaoMap);
return () =>
mapScript.removeEventListener('load', onLoadKakaoMap);
}, [latitude, longitude, mapViewState]);
return(
...
);
}
onLoadKakaoMap 함수에 지도를 그려주는 코드들을 작성하고 mapScript 가 load 되면 실행되고, script 가 load 되지 않으면 사라지도록 했다.
이때 window.kakao.maps 를 인식하지 못하는 에러가 발생한다. 따라서 type을 선언해줘야만 한다.
4. type 선언
declare global {
interface Window {
kakao: any;
}
}
interface MapProps {
latitude: number;
longitude: number;
mapViewState: boolean;
callback: (e: React.MouseEvent) => void;
}
kakao 객체를 window 에서 인식하지 못해 발생하는 에러라고 한다.
global interface를 선언하면서 kakao를 명시해주면 해결된다.
MapProps interface를 보면 이벤트에 대해 명시한 것이 보이는데 그냥 callback: void; 만 입력할 경우 아래와 같은 에러가 발생한다.
콜백함수가 실행되는 이벤트에 대해서 명시해줌으로써 해결했다.
5. 부모 컴포넌트에 이벤트 발생했을 때 실행될 함수(콜백함수) 선언
function Footer() {
const coordinate = {
latitude: 37.50963893461533,
longitude: 127.05797153552354,
};
const [mapViewState, setMapViewState] = useState(false);
const handleMapView = (e: React.MouseEvent) => {
const el = e.target as HTMLElement;
if (el.tagName === 'svg') {
return;
}
setMapViewState(!mapViewState);
};
const handleKey = (e: React.KeyboardEvent) => {
if (e.code === 'Escape') {
setMapViewState(false);
}
};
...
handleMapView는 Map 컴포넌트에서 onClick 이벤트가 발생했을 때 실행될 함수이다.
카카오 맵은 svg 형식으로 되어 있는데, 해당 형식일때 return 하도록 해주지 않으면 지도 위에서도 클릭 이벤트가 발생했을 때 바로 지도가 사라져버린다. 사용자가 지도 안에서 이동, 확대축소 등의 기능을 이용하도록 하기 위해 svg 일 경우 그냥 return 하고 그 바깥을 클릭했을 경우에만 mapViewState가 업데이트 되도록 하였다.
참고자료
'React' 카테고리의 다른 글
React | 리덕스 키워드 정리 (0) | 2022.03.18 |
---|---|
React | 구글 리캡챠(reCaptcha) v2 사용하기 (0) | 2022.02.25 |
React | do not nest ternary expression (Nextjs , typescript, airbnb style guide) (0) | 2022.02.10 |
React | 동적 라우팅 (0) | 2022.01.26 |
React | props와 state (0) | 2022.01.14 |