리액트 카카오맵 라이브러리를 사용하면서 좌표로 주소를 얻어오려 한다.
카카오맵 자바스크립트 aip의 Geocoder 객체를 활용할 생각이다.
npm install react-kakao-maps-sdk
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=YOUR_APP_KEY&libraries=services"></script>
예제 코드
import React, { useEffect, useState } from "react";
import { Map, MapMarker } from "react-kakao-maps-sdk";
const KakaoMapExample = () => {
const [address, setAddress] = useState<string>(""); // 변환된 주소 저장
const [position, setPosition] = useState({ lat: 37.5665, lng: 126.9780 }); // 기본 좌표 (서울시청)
useEffect(() => {
// Geocoder 객체 생성
const geocoder = new window.kakao.maps.services.Geocoder();
// 좌표로 주소를 얻어오는 함수
geocoder.coord2Address(
position.lng, // 경도
position.lat, // 위도
(result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const addressName = result[0]?.address?.address_name || "주소를 찾을 수 없음";
setAddress(addressName); // 변환된 주소 저장
}
}
);
}, [position]);
return (
<div>
<h1>React Kakao Maps SDK 예제</h1>
<Map
center={position}
style={{ width: "100%", height: "400px" }}
onClick={(target, mouseEvent) => {
// 지도 클릭 시 좌표 업데이트
const lat = mouseEvent.latLng.getLat();
const lng = mouseEvent.latLng.getLng();
setPosition({ lat, lng });
}}
>
<MapMarker position={position}>
<div style={{ color: "#000" }}>{address}</div>
</MapMarker>
</Map>
<p>클릭한 위치의 주소: {address}</p> //서울 구로구 구로동 3-25
</div>
);
};
export default KakaoMapExample;
window.kakao.maps.services.Geocoder
는 Kakao Maps API에서 제공하는 객체로, 좌표를 주소로 변환하는 데 사용한다.coord2Address(longitude, latitude, callback)
메서드를 통해 좌표를 주소로 변환한다.coord2Address
의 세 번째 인자로 콜백 함수가 들어가며, 변환 결과와 상태값을 반환한다.status
가 window.kakao.maps.services.Status.OK
인 경우에만 결과를 처리한다.onClick
이벤트를 사용해 클릭한 위치의 좌표를 얻고, 해당 좌표로 주소를 변환한다.카카오맵 SDK의 coord2RegionCode API를 사용하면 위도/경도에 해당하는 행정구역 정보를 정확히 가져올 수 있다.
카카오 지도 API의 coord2Address
메서드를 사용하면 위도, 경도로부터 주소 정보를 가져올 수 있다.
이때 반환되는 result[0]
객체에는 지번 주소와 도로명 주소 정보가 포함되어 있다.
현재 geocoder.coord2Address를 사용해서 위도, 경도로부터 주소를 가져오고 있는데, 카카오 API를 활용하면 좀 더 세부적인 주소 정보를 가져올 수 있다.
const newAddress = result[0]?.address || {};
console.log('newAddress', newAddress);
result
객체를 살펴보면 다음과 같은 정보를 얻을 수 있다.
const newLoaadAddress = result[0].road_address;
console.log('newLoaadAddress', newLoaadAddress);
import { useEffect, useState, useMemo } from "react";
import { Map, MapMarker } from "react-kakao-maps-sdk";
import useFindTheaterQuery from "../../hooks/useFindTheaterQuery";
function TheaterLocation() {
const { isFindLoading, findTheater } = useFindTheaterQuery();
// ✅ useMemo를 사용하여 불필요한 재계산 방지
const center = useMemo(() => {
return { lat: Number(findTheater?.y) || 0, lng: Number(findTheater?.x) || 0 };
}, [findTheater]);
// ✅ 상태 관리
const [centerAddress, setCenterAddress] = useState<string>("");
const [addressInfo, setAddressInfo] = useState<any>(null);
// 지도 클릭 시 주소 저장
const [clickedAddress, setClickedAddress] = useState<string>("");
useEffect(() => {
if (!window.kakao || !center.lat || !center.lng) return;
const geocoder = new window.kakao.maps.services.Geocoder();
// ✅ 지도 중심 주소 가져오기
const fetchAddress = () => {
geocoder.coord2Address(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const newAddress = result[0]?.address || {};
setAddressInfo((prev) => (JSON.stringify(prev) !== JSON.stringify(newAddress) ? newAddress : prev));
}
});
geocoder.coord2RegionCode(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const newCenterAddress = result.find((r) => r.region_type === "H")?.address_name || "";
setCenterAddress((prev) => (prev !== newCenterAddress ? newCenterAddress : prev));
}
});
};
fetchAddress(); // 초기 실행
}, [center]);
// ✅ 지도를 클릭했을 때 해당 좌표의 주소 가져오기
const handleMapClick = (_: any, mouseEvent: kakao.maps.event.MouseEvent) => {
const geocoder = new window.kakao.maps.services.Geocoder();
const lat = mouseEvent.latLng.getLat();
const lng = mouseEvent.latLng.getLng();
geocoder.coord2Address(lng, lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const clickedAddr = result[0]?.address?.address_name || "주소를 찾을 수 없음";
setClickedAddress(clickedAddr);
console.log("클릭한 위치의 주소:", clickedAddr);
}
});
};
return (
<main className="flex flex-col items-center justify-center w-full p-4">
{!isFindLoading && (
<>
{/* 지도 정보 표시 */}
<section className="w-3/4">
<h2 className="text-xl font-semibold text-white bg-gray-700 p-2 rounded-md">
지도 중심 주소: {centerAddress || "주소를 가져오는 중..."}
</h2>
<h2 className="text-xl font-semibold text-white bg-gray-700 p-2 rounded-md mt-2">
지번 주소: {addressInfo?.address_name || "없음"}
</h2>
<h2 className="text-xl font-semibold text-white bg-gray-700 p-2 rounded-md mt-2">
클릭한 위치 주소: {clickedAddress || "지도에서 위치를 클릭하세요."}
</h2>
</section>
{/* 지도 */}
<section className="w-3/4 mt-4">
<Map
id="map"
center={center}
style={{ width: "100%", height: "500px" }}
level={3}
onClick={handleMapClick}
>
<MapMarker position={center} />
</Map>
</section>
{/* 외부 지도 버튼 */}
<section className="flex gap-4 mt-4">
<button
className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg"
onClick={() =>
window.open(
`https://map.kakao.com/link/map/${findTheater?.name},${center.lat},${center.lng}`,
"_blank"
)
}
>
카카오맵에서 보기
</button>
<button
className="bg-gray-600 hover:bg-gray-700 text-white font-semibold py-2 px-4 rounded-lg"
onClick={() =>
window.open(`https://www.google.com/maps/search/?api=1&query=${center.lat},${center.lng}`, "_blank")
}
>
구글맵에서 보기
</button>
<button
className="bg-green-500 hover:bg-green-600 text-white font-semibold py-2 px-4 rounded-lg"
onClick={() =>
window.open(`https://map.naver.com/v5/search/${center.lat},${center.lng}`, "_blank")
}
>
네이버맵에서 보기
</button>
</section>
</>
)}
</main>
);
}
export default TheaterLocation;
문제되는 부분
useEffect(() => {
if (!window.kakao) return;
const geocoder = new window.kakao.maps.services.Geocoder();
geocoder.coord2Address(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const address = result[0]?.address || {};
setAddressInfo(address); // 상태 업데이트 발생
}
});
geocoder.coord2RegionCode(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
for (let i = 0; i < result.length; i++) {
if (result[i].region_type === 'H') {
setCenterAddress(result[i].address_name); // 상태 업데이트 발생
break;
}
}
}
});
}, [center]); // `center` 값이 변경될 때 실행됨
무한 루프 발생 이유
1. useEffect가 실행되면서 setAddressInfo 또는 setCenterAddress가 호출됨.
setState가 호출되면 컴포넌트가 리렌더링됨.
2. 리렌더링 후 center 값이 다시 계산됨 (이 과정에서 useFindTheaterQuery()의 findTheater가 변경될 수도 있음).
3. center 값이 변경되었다고 판단하여 useEffect가 다시 실행됨.
위 과정이 무한 반복되면서 콘솔 로그가 끝없이 출력됨.
해결: useState와 useMemo를 활용한 최적화
useState를 업데이트할 때, 이전 상태와 비교하고, 값이 다를 때만 setState를 실행
const center = useMemo(() => {
return { lat: Number(findTheater?.y) || 0, lng: Number(findTheater?.x) || 0 };
}, [findTheater]);
setAddressInfo((prev) => (JSON.stringify(prev) !== JSON.stringify(newAddress) ? newAddress : prev));
setCenterAddress((prev) => (prev !== newCenterAddress ? newCenterAddress : prev));
const fetchAddress = () => {
geocoder.coord2Address(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const newAddress = result[0]?.address || {};
setAddressInfo((prev) => (JSON.stringify(prev) !== JSON.stringify(newAddress) ? newAddress : prev));
}
});
geocoder.coord2RegionCode(center.lng, center.lat, (result, status) => {
if (status === window.kakao.maps.services.Status.OK) {
const newCenterAddress = result.find((r) => r.region_type === "H")?.address_name || "";
setCenterAddress((prev) => (prev !== newCenterAddress ? newCenterAddress : prev));
}
});
};