24/1/30 카카오맵 API
2024. 1. 31. 08:48ㆍ카테고리 없음
<MapHome.tsx> 오늘은 카카오 맵 API 적용한걸 리뷰해보자.
<"use client";
import { typeOfShop } from "@/app/assets/types/types"; //가게 하나의 type정의 해둔걸 임포트
import { nanoid } from "nanoid"; //map함수 돌렸을떄 최상단 태그의 key값을 나노아이디로 할당했다.
import { useRouter } from "next/navigation";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import { Map, MapMarker } from "react-kakao-maps-sdk"; //카카오맵의 주요태그인 맵과 맵마커
import { useDispatch, useSelector } from "react-redux";
import useKakaoLoader from "../detail/useKaKao"; //최상단에 실행시켜야 카카오맵 기능함
import ColumnSlide from "./ColumnSlide";
import { RootState } from "@/redux/config/configStore"; //툴킷에서 state들을 보관하고 있는 루트스테이트
import { getShop } from "@/redux/modules/detailShopSlice";
import ShopCard from "../home/ShopCard";
import Shopinfo from "../detail/Shopinfo";
import ShopCard2 from "./ShopCard2";
import Image from "next/image";
import upButton from "../../app/assets/images/icon/up.png";
import downButton from "../../app/assets/images/icon/down.png";
type typeOfRef = { //useRef를 아래에서 쓸건데 그에 대한 타입 선언
title: string;
latitude: string;
longitude: string;
};
export default function MapHome() {
useKakaoLoader(); //카카오맵을 실행시키기위한 기초작업
const shops = useSelector((state: RootState) => state.shops); //가게들에 대한 정보를 툴킷에서 가져옴
const router = useRouter();
const mapCenterRef = useRef({ 시군: "", 시도: "" }); //맵 센터에 대한 참조
if (shops[0]) {
const { 시군, 시도 } = shops[0];
mapCenterRef.current = { 시군, 시도 }; //shops 라는 배열에 첫번째 요소가 있다면
} //그 시군과 시도 정보를 ref로 저장해둠
const [slide, setSlide] = useState(0);
const [lat, setLat] = useState(0);
const [lng, setLng] = useState(0);
const [render, setRender] = useState(false);
const [infoToggle, setInfoToggle] = useState(false); //아래에서 쓰일 useState들
// const latRef = useRef(0);
// const lngRef = useRef(0);
const shopsRef = useRef<typeOfRef[]>([]);
const shopInfoRef = useRef<typeOfShop>(shops[0]); //아래에서 쓰일 useRef들
// const [newArray, setnewArray] = useState<typeOfRef[]>([]);
const dispatch = useDispatch(); //툴킷의 state 변화를 일으킬 dispatch 설정
class NewShops { //실전 프로젝트에서 class문법 처음써봤는데 신박했다.ㅋ
title: string;
latitude: string;
longitude: string;
constructor(title: string, latitude: string, longitude: string) {
this.title = title;
this.latitude = latitude;
this.longitude = longitude; //이 붕어빵틀로 객체라는 붕어빵을 뽑아낸다.
}
}
useEffect(() => {
if (!shops[0]) {
router.push("/"); //애초에 배열에 값이 없으면 메인 메뉴로 보내서 검색부터 하게 유도
}
if (window.kakao) { //window.kakao의 존재 여부로 시작되는 로직
let geocoder = new window.kakao.maps.services.Geocoder(); //간결한 변수명으로 할당
geocoder.addressSearch(`${mapCenterRef.current.시도} ${mapCenterRef.current.시군}`, function (result, status) {
// console.log(result, "이거 레절트"); //카카오맵의 기능인데 주소를 넣으면 위도 경도를 가진
setLng(+result[0].x); //result로 반환함
setLat(+result[0].y); //그 위도 경도가 string형이므로 number형으로 바꿔서 state변경
// latRef.current = Number(result[0].y);
// lngRef.current = Number(result[0].x);
});
let mappedArray: { //이런 형식의 array를 하나 만든다.
title: string;
latitude: string;
longitude: string;
}[] = [];
for (let i = 0; i < shops.length; i++) { //배열매서드는 비동기적 통신을 안기다려준다는 말이있어서
let OBOB = { //포문을 돌려봤다. 차이를 체감하지는 못했다.
title: "",
latitude: "",
longitude: ""
};
geocoder.addressSearch(shops[i].주소, function (result, status) { //shops.배열안에 모든 shop들의
OBOB.title = shops[i].업소명; //주소정보로 뒤도경도를 뽑아내려고 한다.
if (result[0]) {
OBOB.latitude = result[0].y;
OBOB.longitude = result[0].x;
}
});
mappedArray.push(OBOB); //이제 빈배열에 push하고
}
shopsRef.current = mappedArray; //그것을 useRef에 담는다.
}
}, [shops]); //디펜던시 어레이는 shops로 하여 shops의 state를 구독한다.
// console.log(newArray, "나오겠지?");
// const moveToDetail = (title: string) => {
// const oneshop = shops.find((shop: typeOfShop) => {
// return shop.업소명 === title;
// });
// // console.log(oneshop);
// if (oneshop) {
// dispatch(getShop(oneshop));
// router.push(`/detail/${oneshop.연락처}`);
// } else alert("존재하지 않아요");
// };
useEffect(() => { //일단 임시로 해둔 화면 랜더링을 1초뒤에 다시하기위한로직
setTimeout(() => {
setRender((prev) => {
return !prev;
});
}, 1000);
}, []);
// const wheelHandler = (e: React.WheelEvent<HTMLDivElement>) => {
// e.preventDefault();
// console.log(e.clientY, "클라와이");
// console.log(e.deltaY, "델타와이");
// console.log(e.movementY, "무브먼트와이");
// setSlide((prev) => {
// if (
// slide >= 0 ||
// Math.ceil((shops.length / 4) * 1000) + slide - 1500 <= 0
// )
// return prev;
// return prev + e.deltaY;
// });
// };
const upHandler = () => { // column슬라이드를 위로 올리기 위한 로직
if (slide >= 0) return;
setSlide(slide + 500);
};
const downHandler = () => { //column슬라이드를 아래로 내리기 위한 로직
if (Math.ceil((shops.length / 4) * 1000) + slide - 1500 <= 0) return;
setSlide(slide - 500); //지금은 column슬라이드 안에 따로 함수를 넣어서
}; //필요없어서 지워야됨 ㅋㅋㅋ 지금 발견함
const openShopInfo = (title: string) => {
const foundShop = shops.find((shop: typeOfShop) => { //맵마커를 누를시 매개변수로 받은값과
return shop.업소명 === title; //shops 중 업소명이 같은 shop을 골라내는 작업
});
if (foundShop) {
shopInfoRef.current = foundShop; //찾은걸 foundShop에 담아줌
}
setInfoToggle((prevInfoToggle) => !prevInfoToggle); //여기서 인포토글을 통해 shopcard를 온오프
};
return (
<>
<div className="w-screen h-screen bg-yellow-300">
<div className="bg-green-300 absolute top-[75px] left-[450px] z-10">{/* <SearchForm /> */}</div>
<Map
onClick={() => setInfoToggle(!infoToggle)} //맵을 누를시 온오프
// ref={mapRef.current}
className="bg-yellow-100" // 지도를 표시할 Container
id="map"
center={{
// 지도의 중심좌표
lat, //key 와 value가 같기 때문에 하나로 통일
lng
}}
style={{
// 지도의 크기
width: "100%",
height: "100%"
}}
level={6} // 지도의 확대 레벨
>
{shopsRef.current.map((shop: typeOfRef) => { //shopsRef로 저장된 값으로 화면에 맵마커들을
return ( //뿌려준다.
<MapMarker
key={nanoid()} //key값 딱히 사용처가 없어서 일단 nanoid부여
position={{ lat: +shop.latitude, lng: +shop.longitude }}
title={shop.title}
onClick={() => openShopInfo(shop.title)}
>
{/* <div className="flex justify-center items-center w-[140px] p-[5px] m-[5px] font-black">
{shop.title}
</div> */}
{infoToggle && shop.title === shopInfoRef.current.업소명 && (
<>
<ShopCard2 type="map" shops={shops} shop={shopInfoRef.current} />{" "}
</> //이곳에서 shopcard를 토글해준다.
)} //프롭스로 shops와 shop에 대한 정보전달.
{/* <div>클릭하시면정보나옴</div> */} //사실 shops정보는 필요없다 .ㅋㅋㅋ
</MapMarker> //로직짜고나서 한참뒤에 shops정보 왜 프롭스로 줬었지?
); //바본가라고 생각함 ㅋㅋ
})}
{/* <MapMarker
position={{
lat,
lng,
}}
title="현재위치"
image={{
src: "https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_red.png", // 마커이미지의 주소입니다
size: {
width: 64,
height: 69,
}, // 마커이미지의 크기입니다
options: {
offset: {
x: 27,
y: 69,
}, // 마커이미지의 옵션입니다. 마커의 좌표와 일치시킬 이미지 안에서의 좌표를 설정합니다.
},
}}
/> */}
</Map>
</div>
<div className="absolute top-[64px] z-10 h-full flex items-center">
<div
// onWheel={(e) => wheelHandler(e)}
// className={`flex flex-col h-[900px]`}
className="flex flex-col my-[30px] h-full"
>
{/* <div
style={{
height: `${Math.ceil(shops.length / 4) * 1000}px`,
transform: `translate(0,${slide}px)`,
transition: "transform 0.5s",
display: "flex",
}}
>
</div> */}
<ColumnSlide />
</div>
{/* <div className="flex flex-col justify-center gap-5 h-screen ml-[20px] ">
<button
onClick={upHandler}
className=" bg-purple-300 rounded-full text-4xl hover:scale-110"
>
👆
</button>
<Image
onClick={upHandler}
src={upButton}
alt="upButton"
className="w-[30px] h-[30px] cursor-pointer"
/>
<button
onClick={downHandler}
className="bg-purple-300 rounded-full text-4xl hover:scale-110"
>
👇
</button>
<Image
onClick={downHandler}
src={downButton}
alt="downButton"
className="w-[30px] h-[30px] cursor-pointer"
/>
</div> */}
</div>
</>
);
}
//일단 미완성 로직이다.
//이유는 이미 알고 있다. 화면에 랜더링시 마커가 제대로 찍히지 않거나 해당 값이 없다고 뜬다.
//이유는 카카오맵 API에서 주소로 위도경도를 찾아주는것은 비동기 로직인데 이 프로미스값을 기다리지 않고
//다음 로직이 작동 되기 때문이라고 추측한다.
//일단 결과적으로 에러는 뜨지만 1초뒤 정상적으로 마커를 그려주기 때문에 후순위로 보류해뒀다.