지도에 커스텀 오버레이 생성하기
카카오맵 api 공식 문서를 참고해서 지도에 자유롭게 컨텐츠를 표시할 수 있는 커스텀 오버레이를 생성한다. 마커를 클릭할 시 식당의 상호명과 주소를 적당한 ui 안에 나타내기로 했다.
◆ Custom Hook으로 분리하기
그 전에 점점 길어지고 있는 코드를 정리할 필요가 있었다. supabase에서 식당의 위도와 경도 정보를 가져와서 해당 좌표에 마커를 생성하는 지도 컴포넌트인데, 기능을 추가하다 보니 뭐가 참 많다(...) supabase에서 restaurants 테이블의 정보를 가져오는 부분은 다른 팀원들도 활용할 가능성이 있어서 따로 커스텀 훅을 만들면 좋을 것 같다.
import { useEffect, useState } from 'react';
import { supabase } from '../supabase/supabaseClient';
const useFetchRestaurants = () => {
const [restaurants, setRestaurants] = useState([]);
useEffect(() => {
// fetchRestaurants: supabase에서 데이터를 가져오는 함수
const fetchRestaurants = async () => {
const { data } = await supabase.from('restaurants').select('*');
setRestaurants(data);
};
fetchRestaurants();
}, []);
return restaurants;
};
export default useFetchRestaurants;
◆ 커스텀 오버레이 내용 작성하기
처음에는 react 컴포넌트 만들던 습관 그대로 OverlayContent.jsx 파일을 만들어 신나게 ui를 만들고 import해서 가져오겠다는 생각이었다. 그런데 정작 렌더링이 정상적으로 이루어지지 않았는데, 콘솔 오류 메세지를 확인해보니 parameter 1 is not of type Node 란다.
// 좌표에 커스텀 오버레이를 생성한다.
const overlay = new kakao.maps.CustomOverlay({
// 문제.
// OverlayContent는 리액트 컴포넌트이고, content 속성은 HTML 문자열 또는 DOM 노드여야 한다.
// 이로 인해 콘솔에 'parameter 1 is not of type Node'오류가 생긴다.
// 해결법.
// 1. 컴포넌트를 html로 변환해서 전달한다.
// 2. 직접 dom 노드를 생성해서 오버레이를 만드는 코드를 작성한다.
// 3. 커스텀 오버레이 내용을 그냥 ``로 만들어서 넣는다.
content: OverlayContent
map: null,
position: markerCoordinates,
removeable: true // 오버레이 제거 기능
});
OverLayContent는 리액트 컴포넌트이고, content 속성은 HTML 문자열 또는 DOM 노드여야 하기 때문에 문제가 생겼다. 해결법은 단순했다.
- 컴포넌트를 HTML로 변환해서 전달한다.
- 직접 DOM 노드를 생성해서 커스텀 오버레이를 만드는 코드를 작성한다.
- 커스텀 오버레이 내용을 ``로 만들어 넣는다.
방법은 다양했는데, 우선 ui 모양을 보고 싶어서 커스텀 오버레이 내용을 `` 안에 그대로 만들어 보았다. 들어갈 내용이 팀원들과의 논의 하에 변동될 수 있기 때문이다. useCallback은 강의에서 배운 내용을 잊어버리기 전에 적용해본 건데, 사실 여기서는 메모리를 낭비하는 꼴이라 굳이 필요하지 않을 것 같다.
// 전체 코드
import { useCallback, useEffect, useRef } from 'react';
import useFetchRestaurants from './useFetchRestaurants';
// import Overlaycontent from './Overlaycontent';
const MapComponent = () => {
const mapContainer = useRef(null);
const restaurants = useFetchRestaurants();
// createMarker: 마커, 인포윈도우를 생성하는 함수
const createMarker = useCallback((map, restaurant) => {
const { kakao } = window;
// 좌표에 마커를 생성한다.
const markerCoordinates = new kakao.maps.LatLng(restaurant.latitude, restaurant.longitude);
const marker = new kakao.maps.Marker({
position: markerCoordinates
});
// 커스텀 오버레이 내용을 작성한다.
const overlayContent = `
<div style="border-radius:5px; overflow:hidden; width:250px; text-align:center; font-size:12px; border:1px solid #333; background:#fff;">
<div style="padding:10px; font-weight:bold;">${restaurant.name}</div>
<div style="padding:5px;">
${
restaurant.img_url
? `<img src="${restaurant.img_url}" alt="${restaurant.name}" style="width:100%; height:auto;" />`
: ''
}
</div>
<div style="padding:5px;">주소: ${restaurant.address}</div>
${restaurant.tel ? `<div style="padding:5px;">전화번호: ${restaurant.tel}</div>` : ''}
</div>
`; // 으악 더러워
// 좌표에 커스텀 오버레이를 생성한다.
const overlay = new kakao.maps.CustomOverlay({
content: overlayContent,
map: null,
position: markerCoordinates,
removeable: true // 오버레이 제거 기능
});
// 마커를 지도에 추가한다.
marker.setMap(map);
// 마커에 클릭 이벤트를 등록한다.
kakao.maps.event.addListener(marker, 'click', function () {
overlay.setMap(map);
});
}, []);
// initializeMap: 지도와 마커를 설정하는 함수(지도 초기화)
const initializeMap = useCallback(
(restaurants) => {
const { kakao } = window;
const mapOption = {
center: new kakao.maps.LatLng(37.5665, 126.978), //지도의 중심좌표
level: 5 // 지도의 확대 레벨 (3 ~ 5 사이 추천)
};
const map = new kakao.maps.Map(mapContainer.current, mapOption); // 지도
const infowindow = new kakao.maps.InfoWindow({ zIndex: 1 }); // 인포윈도우
// 마커를 생성하고 지도에 표시한다.
restaurants.forEach((restaurant) => {
createMarker(map, restaurant, infowindow);
});
},
[createMarker]
);
// 카카오맵 api에서 마커를 표시한다.
useEffect(() => {
if (restaurants.length > 0) {
initializeMap(restaurants);
}
}, [restaurants, initializeMap]);
return (
<>
<div
ref={mapContainer}
style={{
width: '1000px',
height: '1000px'
}}
></div>
</>
);
};
export default MapComponent;
'스파르타코딩클럽 > 과제' 카테고리의 다른 글
메인페이지, 레이아웃 구성 (1) | 2024.12.18 |
---|---|
[Next.js + TypeScript] 리그 오브 레전드 정보 도감 만들기 (0) | 2024.12.11 |
[뉴스피드 프로젝트] 게시글 작성 페이지_이미지 업로드 (1) | 2024.11.18 |
[뉴스피드 프로젝트] 1일차 - 기획 단계 (1) | 2024.11.15 |
[React 숙련] 1주차_React Router Dom (1) | 2024.11.08 |