-
React์์ TMap API ์ฌ์ฉํ๊ธฐTECH 2023. 12. 30. 14:48
์ด ๊ธ์์๋ ๋ค์ด๋ฒ ๋ถ์คํธ์บ ํ ๋ด์ ๋ชจ๊ฐ์ฝ ๋ชจ์ง/๊ด๋ฆฌ ํ๋ซํผ์ธ ๋ชจ๋ฝ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๋ฉฐ TMap API๋ฅผ ์ฌ์ฉํ๋ ํ๊ธฐ๋ฅผ ๋ด์ ๋ณด์์ต๋๋ค.
TMAP Open API๋ ์น ๊ฐ๋ฐ ๋๋ ์ดํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์ ๋ค์ํ๊ฒ ํ์ฉ๋ ์ ์๋๋ก, Javascript ํํ๋ก ์ ๊ณต๋๋ TMAP ์ง๋ ํ๋ซํผ์ ๋๋ค. TMap API๋ ์ด๋ฏธ์ง๋ก ์ง๋๋ฅผ ๊ทธ๋ฆฌ๋ Raster Map(V2)๊ณผ ์ค์๊ฐ์ผ๋ก ์ง๋๋ฅผ ๊ทธ๋ฆฌ๋ Vector Map(V3) ๋ ๊ฐ์ง๋ฅผ ์ง์ํ๋ฉฐ Vector Map์ ํ๋/์ถ์๊ฐ ์์ฐ์ค๋ฝ๊ณ ํ์ ๊ธฐ๋ฅ์ด ์๋ ์ฐจ์ด์ ์ด ์๋ค๊ณ ํ๋ ์ฐธ๊ณ ํ์๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค. ์ง์ ์ฌ์ฉํด ๋ณด์์ ๋๋ ์ง์ํ๋ ๊ธฐ๋ฅ์ Raster Map์ด ๋ ๋ง์์ต๋๋ค.
๋ํ, ์ด ๊ธ์์ ์ค๋ช ํ๋ ๋ฐฉ์์ Raster Map ๋ฐฉ์์ธ TMapv2๋ฅผ ์ฌ์ฉํ์ต๋๋ค. TMapv3์ ์ ์ฉํ ๋๋ ๋ฉ์๋๋ช ์ด๋ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ๋ค๋ฅผ ์ ์์ผ๋, ์์ธํ ์ฌํญ์ TMap์ ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
๊ธฐ๋ฅ ์ค๋ช
๋ชจ๋ฝ์์๋ ๋ชจ๊ฐ์ฝ๋ฅผ ์ฃผ์ตํ ๊ฒฝ์ฐ ํผ์ ์์ฑํ์ฌ ๊ธ์ ๋ฑ๋กํฉ๋๋ค. ํผ ์์ฑ ์ ์ฃผ์๋ฅผ ์ ๋ ฅํ๋ ๋ถ๋ถ์ด ์๋๋ฐ, ์ด ๋ถ๋ถ์ ๋จ์ ํ ์คํธ๊ฐ ์๋ ์ฃผ์ ํ์์ ํ ์คํธ๋ก ์ ๋ ฅ๋ฐ๊ธฐ ์ํด TMap API๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
์ง๋ ๋์ฐ๊ธฐ
import { useEffect, useState } from 'react'; import { TMap } from '@/types'; const { Tmapv2 } = window; export const useMap = (mapRef: React.RefObject<HTMLDivElement>) => { const [mapInstance, setMapInstance] = useState<TMap | null>(null); useEffect(() => { if (mapRef.current?.firstChild || mapInstance) { return; } const map = new Tmapv2.Map('map', { zoom: DEFAULT_ZOOM_LEVEL, zoomControl: false, center: new Tmapv2.LatLng(INITIAL_LATITUDE, INITIAL_LONGITUDE), }); map.setZoomLimit(MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL); setMapInstance(map); }, [mapRef, mapInstance]); };
์ฐ์ ์ง๋๋ ๋ ๋๋งํ ๊ฒ ํ๋๋ง ๋์ฐ๋ฉด ๋ฉ๋๋ค. ์ง๋๋ฅผ ๋์ฐ๊ธฐ ์ํ
useMap
ํ ์ ์์ฑํ์ต๋๋ค.useMap
์ ์ธ์์ธmapRef
๋ ์ค์ ํ ์ง๋์ div ํ๊ทธ๋ฅผ ์ฐ๊ฒฐํด ์ฃผ๊ธฐ ์ํด ์ฌ์ฉํ์ต๋๋ค.๋ํ ์ดํ์
new Tmapv2.Map
๋ก ์์ฑํ map ๋ณ์์ ์ ๊ทผํ์ฌ ์ง๋๋ฅผ ์กฐ์ํ ํ์๊ฐ ์๋๋ฐ, ์ด๋ฅผ ์ํดmapInstance
state๋ฅผ ๋ง๋ค์ด ์ ์ฅํด ๋์์ต๋๋ค.useEffect ๋ด์ if ๋ฌธ์์
mapRef.current?.firstChild
์mapInstance
๋ฅผ ๊ฒ์ฌํ ์ด์ ๋, ์ง๋ ๊ฐ์ฒด๊ฐ ๋ ๊ฐ ์์ฑ๋๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ์์๊ธฐ ๋๋ฌธ์ ๋ค์ ํฌ์คํ ์ ์ฐธ๊ณ ํ์ฌ ํด๊ฒฐํ์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๋ชจ๋ฌ์ ์ง๋๋ฅผ ๋์ธ ์ ์์ต๋๋ค.
๊ฒ์์ด๋ก ์ฅ์ ์ ํํ๋ฉด ์ง๋์ ํ์ผ๋ก ๋ณด์ฌ ์ฃผ๊ธฐ
์ฅ์ ๋ชจ๋ฌ์ ์ผ์ชฝ ์๋จ์ ์ธํ์ ์ฅ์ ํค์๋๋ฅผ ๊ฒ์ํ๋ฉด ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ๋ฆฌ์คํธ ํ์์ผ๋ก ์ถ๋ ฅ๋๋ฉฐ, ๋ฆฌ์คํธ ๋ด์ ์์ดํ ์ ํด๋ฆญํ๋ฉด ์ฅ์๊ฐ ์ ํ๋ฉ๋๋ค. ์ฌ์ฉ์์๊ฒ ์ ํํ ์ฅ์๋ฅผ ์ง๋ ๋ด์์๋ ๋ณด์ฌ ์ฃผ๊ธฐ ์ํด ํ์ผ๋ก ํ์ํด ์ฃผ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์ต๋๋ค.
import { useCallback, useEffect, useState } from 'react'; import { Marker } from '@/components/Map/Marker'; import { TMap, TMapMarker } from '@/types'; const { Tmapv2 } = window; export const useMap = (mapRef: React.RefObject<HTMLDivElement>) => { const [mapInstance, setMapInstance] = useState<TMap | null>(null); const [currentMarker, setCurrentMarker] = useState<TMapMarker | null>(null); // ์ง๋ ๋์ฐ๋ ์ฝ๋ ์๋ต const updateMarker = useCallback( (coord: { latitude: number | null; longitude: number | null }) => { const { latitude, longitude } = coord; if (!(latitude && longitude) || !mapInstance) { return; } if (currentMarker) { const { _lat: prevLatitude, _lng: prevLongitude } = currentMarker.getPosition(); if (prevLatitude === latitude && prevLongitude === longitude) { return; } } currentMarker?.setMap(null); const position = new Tmapv2.LatLng(latitude, longitude); const marker = new Tmapv2.Marker({ position, map: mapInstance, icon: markerIcon, iconSize: new Tmapv2.Size(50, 50), }) setCurrentMarker(marker); mapInstance?.setCenter(position); }, [mapInstance, currentMarker], ); return { mapInstance, updateMarker }; };
์ฐ์
useMap
ํ ์์ ์ฅ์์ ์ ํ์ ๋ฐ๋ผ ์ง๋์ ๋ง์ปค๋ฅผ ์ฐ์ด ์ฃผ๋updateMarker
๋ผ๋ ๋ฉ์๋๋ฅผ ๋ง๋ค์์ต๋๋ค.updateMarker
์์๋ ์ธ์๋ก ๋ฐ์ coord์ ์๋/๊ฒฝ๋์ ์ด๋ฏธ ์ฐํ์ ธ ์๋ marker์ ์๋/๊ฒฝ๋ ๊ฐ์ ๋น๊ตํฉ๋๋ค.์ธ์๋ก ๋ฐ์ coord๊ฐ ์ ํจํ์ง ์๊ฑฐ๋, coord๊ฐ ๋ณ๊ฒฝ๋์ง ์์๋ค๋ฉด
updateMarker
๋ ๋ง์ปค์ ์์น๋ฅผ ๋ณ๊ฒฝํ๋ ๋ก์ง์ ์คํํ์ง ์์ต๋๋ค.๋ง์ผ coord์ ์๋/๊ฒฝ๋๊ฐ ๋ณ๊ฒฝ๋์๋ค๋ฉด,
currentMarker.setMap(null)
๋ก ๋ง์ปค๋ฅผ ์ง๋์์ ์ง์ฐ๊ณ ์๋ก์ด ์ปค์คํ ๋ง์ปค๋ฅผ ์ง๋์ ์ถ๊ฐํด ์ค๋๋ค.๊ทธ๋ฆฌ๊ณ
mapInstance.setCenter(position)
์ผ๋ก ์๋ก ์ฐ์ ๋ง์ปค๊ฐ ์ง๋ ์ค์์ ํ์๋๋๋ก ๋ณด์ฌ ์ค๋๋ค.TMapLatLng
์ ๊ฐ์ ํ์ ์ TMap API Guide๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ฑํ์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋งต ๋ชจ๋ฌ์์๋
useMap
ํ ์updateMarker
๋ฉ์๋๋ฅผ ์ด์ฉํฉ๋๋ค.์ฐ์ ์ฅ์ ๋ฆฌ์คํธ ์ค ํ๋์ ์ฅ์๋ฅผ ์ ํํ๊ธฐ ์ํด ํด๋ฆญํ๋ฉด
onClickAddressListItem
๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ํด๋น ๋ฉ์๋๋ coord์ ์ ํํ ์ฅ์์ ์๋์ ๊ฒฝ๋๋ฅผ ์ ์ฅํฉ๋๋ค.import { useEffect, useRef, useState } from "react"; import { useMap } from "./useMap"; export function MapModal() { const [selectedAddress, setSelectedAddress] = useState(""); const [coord, setCoord] = useState<{ latitude: number | null; longitude: number | null; }>({ latitude: null, longitude: null, }); const mapRef = useRef<HTMLDivElement>(null); const { updateMarker } = useMap(mapRef); useEffect(() => { updateMarker(coord); }, [coord, updateMarker]); const onClickAddressListItem = < Event extends React.MouseEvent | React.KeyboardEvent, >( e: Event, ) => { setSelectedAddress(e.currentTarget.getAttribute("value") || ""); const coordinate = { latitude: Number(e.currentTarget.getAttribute("data-lat")), longitude: Number(e.currentTarget.getAttribute("data-lon")), }; setCoord(coordinate); }; return ( // ์๋ต ); }
๊ทธ๋ฆฌ๊ณ coord๊ฐ ๋ณ๊ฒฝ๋์๋ค๋ฉด,
updateMarker
๋ฅผ ํธ์ถํด ์ง๋์ ๋ง์ปค๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ฅ์ ๋ฆฌ์คํธ๋ฅผ ํด๋ฆญํ์ ๋ ์ง๋์ ๋ง์ปค๋ฅผ ๋์ธ ์ ์๊ฒ ๋์์ต๋๋ค.
์ง๋ ํด๋ฆญ ์ ๋ง์ปค ๋์ฐ๊ธฐ
์ฅ์๋ฅผ ํ ์คํธ๋ก ๊ฒ์ํ๋ ๊ฒ ์ธ์๋ ์ง๋๋ฅผ ํด๋ฆญํ์ฌ ์ํ๋ ์ฅ์๋ฅผ ์ ํํ ์ ์๋ ๊ธฐ๋ฅ์ ๊ตฌํํ์ต๋๋ค.
T Map ํด๋ฆญํ ์์น์ ๋ง์ปค ํ์ํ๊ธฐ ์์์ ๋์จ ๋๋ก ๊ตฌํ์ ์๋ํ์ผ๋, ๋ค์๊ณผ ๊ฐ์ด ๋ง์ปค๊ฐ ์ฌ๋ฌ ๊ฐ ์ฐํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
๋ฐ๋ผ์ ์์์ ๊ฐ์ ๋ฐฉ์์ด ์๋
currentMarker
๋ก ๋ง์ปค๋ฅผ ๊ด๋ฆฌํ๊ณ , ๋ง์ปค์ position์ ๋ฐ๊พธ์ด ์๋ก์ด ์์น์ ๋ง์ปค๋ฅผ ์ฐ์ด ์ฃผ๋ ๋ฐฉํฅ์ผ๋ก ๊ตฌํํ์ต๋๋ค.
์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.import { useEffect, useState } from "react"; import { Marker } from "@/components/Map/Marker"; import { TMap, TMapMarker } from "@/types"; const { Tmapv2 } = window; export const useMap = (mapRef: React.RefObject<HTMLDivElement>) => { const [mapInstance, setMapInstance] = useState<TMap | null>(null); const [currentMarker, setCurrentMarker] = useState<TMapMarker | null>(null); useEffect(() => { if (!mapInstance) { return; } mapInstance.addListener("Click", (e) => { const { latLng } = e; const position = new Tmapv2.LatLng(latLng.lat(), latLng.lng()); if (!currentMarker) { const marker = new Tmapv2.Marker({ position, map: mapInstance, icon: markerIcon, iconSize: new Tmapv2.Size(50, 50), }) setCurrentMarker(marker); } else { currentMarker.setPosition(position); } }); }, [mapInstance, currentMarker]); };
์ฌ๊ธฐ์
currentMarker
๊ฐ ์๋ค๋ฉด ์๋ก์ด ๋ง์ปค๋ฅผ ์์ฑํ์ฌsetCurrentMarker
์ ํ ๋นํ๊ณ ,currentMarker
๊ฐ ์๋ค๋ฉดsetPosition
๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ง์ปค์ ํฌ์ง์ ๋ง ๋ฐ๊ฟ์ ์ฌ์ฉํ ์ ์๋๋ก ๊ตฌํํ์ต๋๋ค.
๊ทธ๋ฌ๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ๋๋, ๋ ๋ค๋ฅธ ๋ฌธ์ ์ ์ง๋ฉดํ์ต๋๋ค. (๋ฒ๊ทธ ์ณ๋ฐํด...)
์ฅ์ ํด๋ฆญ ์ ์๊ธฐ๋ ๋ง์ปค์ ์ง๋ ํด๋ฆญ ์ ์๊ธฐ๋ ๋ง์ปค๊ฐ ๋ฐ๋ก ์กด์ฌํ๋ค๋ ๋ฌธ์ ๋ ๋ฐ์ํ์ต๋๋ค.
์ด๋ฅผ ์ํด ์ง๋๋ ์ฅ์๋ฅผ ํด๋ฆญํ์ฌ ์ ํํ ์ฃผ์๋ฅผ ๋ณ๊ฒฝํ ๊ฒฝ์ฐ
currentCoord
(์๋/๊ฒฝ๋) ๊ฐ์ ๋ฐ๊พธ๊ณ ,currentCoord
๊ฐ ๋ณ๊ฒฝ๋์์ ๋ ๋ณ๊ฒฝ๋ ์๋/๊ฒฝ๋์ ๋ง์ปค๋ฅผ ์ฐ์ด ์ฃผ๋๋ก ๋ก์ง์ ๋ณ๊ฒฝํ์ฌ ์ฅ์ ์ ํ ๊ธฐ๋ฅ์ ์์ฑํ์ต๋๋ค.import { useEffect, useState } from 'react'; import { TMap, TMapEvent, TMapLatLng, TMapMarker } from '@/types'; const { Tmapv2 } = window; export const useMap = (mapRef: React.RefObject<HTMLDivElement>) => { const [mapInstance, setMapInstance] = useState<TMap | null>(null); const [currentMarker, setCurrentMarker] = useState<TMapMarker | null>(null); const [currentCoord, setCurrentCoord] = useState<TMapLatLng | null>(null); useEffect(() => { if (!currentCoord) { return; } const makeMarker = (position: TMapLatLng) => { if (!mapInstance) { return; } currentMarker?.setMap(null); const marker = new Tmapv2.Marker({ position, map: mapInstance, icon: markerIcon, iconSize: new Tmapv2.Size(50, 50), }) setCurrentMarker(marker); }; makeMarker(currentCoord); }, [currentCoord, mapInstance]); useEffect(() => { if (!mapInstance) { return; } const changeCoord = (e: TMapEvent) => { const { latLng } = e; const position = new Tmapv2.LatLng(latLng.lat(), latLng.lng()); setCurrentCoord(position); }; mapInstance.addListener('click', changeCoord); }, [mapInstance, currentCoord]); };
ํ๊ธฐ
๋ค์๊ณผ ๊ฐ์ด ์ง๋ API๋ฅผ ์ฌ์ฉํ์ฌ ์๋น์ค์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์์ต๋๋ค. TypeScript๋ฅผ ์ง์ํ์ง ์๋ API์ ํ์ ์ ์ผ์ผ์ด ์์ฑํ์ฌ ์ฌ์ฉํ๋ ๊ฒ๋ ์ฒ์์ ๊น๋ค๋ก์ ๊ณ , JavaScript์ ์์๋ฅผ React์ ์ ์ฉํ๋ ค๋ค ๋ณด๋ ์ด๋ ค์ด ์ ๋ ๋ง์์ง๋ง ๋ฌธ์ ๋ฅผ ํ์ ํ๊ณ ๋ถ์ํ๋ฉฐ ๋ค๋ฅธ ํด๊ฒฐ ๋ฐฉ์์ ๋ชจ์ํ๋ ๊ธธ์ ํตํด ํ ๊ฑธ์ ๋ ์ฑ์ฅํ ์ ์๋ ๊ณ๊ธฐ๊ฐ ๋์ง ์์๋ ์๊ฐํฉ๋๋ค.
TMap API๋ฅผ React์ ์ ์ฉํ๋ ๊ธ์ด ๋ง์ง ์์๊ธฐ ๋๋ฌธ์, ์ ์ ํฌ์คํ ์ผ๋ก ์กฐ๊ธ์ด๋๋ง ๋์์ด ๋ ์ ์์์ผ๋ฉด ์ข๊ฒ ์ต๋๋ค. ์์ธํ ์ฝ๋๋ฅผ ์ดํด๋ณด๊ณ ์ถ์ผ์๋ค๋ฉด ๋ฆฌํฉํ ๋ง ์ค์ธ ์ฝ๋์ง๋ง ์ฐธ๊ณ ํด ์ฃผ์๊ณ โฌ๏ธโฌ๏ธโฌ๏ธ ๋ ๋ง์ ๋์์ด ํ์ํ์๊ฑฐ๋ ํผ๋๋ฐฑ์ ์ฃผ๊ณ ์ถ๋ค๋ฉด ๋๊ธ๋ก ์๊ฒฌ์ ๋จ๊ฒจ ์ฃผ์ ๋ ์ข์ต๋๋ค!
https://github.com/boostcampwm2023/web17_morak/blob/develop/app/frontend/src/hooks/useMap.ts
'TECH' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React์์ Event Handler๋ ์ด๋ป๊ฒ ๋์ํ ๊น์? (0) 2024.01.11 Zero Runtime CSS-in-JS์ ๋ํด ์์๋ณด์ (1) 2024.01.06 ์ ์ ์์ ์ ์ src์์ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ข์๊น? (2) 2023.12.16 CSS์ ์ฐ์ ์์, CSS Cascading (0) 2023.09.04 ๋ชจ๋ ธ๋ ํฌ ๋์ ํ๊ธฐ(Feat. Nx) (4) 2023.08.27