React์์ TMap API ์ฌ์ฉํ๊ธฐ
์ด ๊ธ์์๋ ๋ค์ด๋ฒ ๋ถ์คํธ์บ ํ ๋ด์ ๋ชจ๊ฐ์ฝ ๋ชจ์ง/๊ด๋ฆฌ ํ๋ซํผ์ธ ๋ชจ๋ฝ ์๋น์ค๋ฅผ ๊ฐ๋ฐํ๋ฉฐ TMap API๋ฅผ ์ฌ์ฉํ๋ ํ๊ธฐ๋ฅผ ๋ด์ ๋ณด์์ต๋๋ค.
GitHub - boostcampwm2023/web17_morak: Morak | ๋ค์ด๋ฒ ๋ถ์คํธ์บ ํ ๋ด ๋ชจ๊ฐ์ฝ ๋ชจ์ง/๊ด๋ฆฌ ํ๋ซํผ ๐ง๐ป๐ป
Morak | ๋ค์ด๋ฒ ๋ถ์คํธ์บ ํ ๋ด ๋ชจ๊ฐ์ฝ ๋ชจ์ง/๊ด๋ฆฌ ํ๋ซํผ ๐ง๐ป๐ป๐ฉ๐ป๐ป๐จ๐ป๐ป. Contribute to boostcampwm2023/web17_morak development by creating an account on GitHub.
github.com
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