🤔 커스텀 훅을 사용한 이유?
사실 처음부터 커스텀 훅을 사용해 만들 생각은 없었다.
처음 프로젝트를 만들 당시 카카오 맵 API를 활용한 중복된 코드가 많았고, 이를 불편하게 생각한 나는 리팩토링 과정에서 꼭 수정해야겠다고 다짐했다.
가장 불편했던 점은 지도와 인터랙션을 하기 위해서는 맵 객체를 생성해야 한다는 점이였다. 나는 프로젝트 요구사항을 만족시키기 위해 마커생성, 클릭이벤트, 클러스터러 기능 등 여러 가지를 수행해야 했다. 따라서 그럴 때마다 Map 객체를 만드는 과정을 반복해야 했다.
그래서 나는 Map객체를 공용으로 사용하는 인스턴스를 가진 커스텀 훅을 만들기로 했다.
export default function useMap(containerRef: RefObject<HTMLElement>) {
// 클릭한 곳 마커 생성 및 주소 반환
const displayInitMarker = async () => {
...
};
// 지도 움직일 때마다 중심 좌표 구하는 함수
const getCenterLatLng = async () => {
...
};
// 현위치로 이동 함수
const placeCurLocation = async () => {
...
};
// 모든 리뷰 보기 && 클릭한 마커의 리뷰 보기 && 클러스트러 기능
const viewAllReviews = async () => {
...
};
// 지도 영역 마커만 출력
const viewReviewInMap = async () => {
...
};
// 검색한 주소로 이동 함수
const placeSearchLocation = async () => {
...
};
// 지도 초기 위치
useEffect(() => {
(async () => {
if (containerRef.current) {
// 맵 객체 생성
}
})();
}, [containerRef]);
return {
map,
placeCurLocation,
placeSearchLocation,
displayInitMarker,
getCenterLatLng,
viewAllReviews,
viewReviewInMap,
};
}
대략 이런식으로 만들 예정이다. 구조는 다 비슷하게 만들었기 때문에 하나의 인스턴스만 예를 들어 설명을 해보겠다.
실습 : 현위치로 이동 하기
요구조건은 다음과 같다.
- 현위치로 이동한다.
- 이동한 위치의 주소를 함께 반환한다.
1. 맵 객체 생성
export default function useMap(containerRef: RefObject<HTMLElement>) {
// 지도 초기 위치
useEffect(() => {
(async () => {
if (containerRef.current) {
// 초기 위치(현재위치)
const initMap = new window.kakao.maps.Map(
containerRef.current,
{
center: new window.kakao.maps.LatLng(mylat, mylng),
level: `${centerLevel}`,
},
);
// 마커 이미지를 생성합니다
let markerImage = new window.kakao.maps.MarkerImage(
mapMascot,
new window.kakao.maps.Size(64, 69),
);
let markerPosition = new window.kakao.maps.LatLng(mylat, mylng);
// 마커를 생성합니다
let initmarker = new window.kakao.maps.Marker({
position: markerPosition,
image: markerImage, // 마커이미지 설정
map: initMap,
});
setMap(initMap); // 지도 생성하기
setMarker(initmarker); // 마커 생성하기
const addr = await coordToAddress(mylat, mylng);
setAddress(addr);
}
})();
}, [containerRef]);
return {map};
}
우선 공통으로 사용될 맵 객체를 생성해준다. 여기서 매개변수로 사용된 containerRef는 맵 객체가 들어갈 DOM요소다.
(여기서 사용되는 mylat, mylng, centerLevel 변수는 위도, 경도, 지도 크기 값을 나타낸다. 나는 두개의 컴포넌트에서 하나의 지도를 컨트롤 하다보니 컴포넌트를 이동했을 때 지도의 동기화 작업이 필요했다. 따라서 위도, 경도, 지도 크기값을 전역 상태에서 관리를 해주는 방법을 사용했다. coordToAddress는 좌표를 주소로 변환시켜주는 함수로 좀 이따 설명 할 예정이다.)
const newMap = useRef(null);
const {
... // 인스턴스들
} = useMap(newMap); // 지도 관련 훅
<_kakaoMapWrapper ref={newMap} className="newMap">
따라서 사용될 컴포넌트 DOM 요소에 위 코드와 같이 넣어주면 된다. 그러면 지도가 생성되는 모습을 볼 수 있다.
2. 현위치로 이동하는 인스턴스 만들기
const placeCurLocation = async () => {
if (map) {
// 현위치로 이동 함수
}
};
나는 현위치 이동 코드를 인스턴스에 작성하면 길이가 너무 길어질 것 같아서 함수로 로직을 빼내서 관리했다.
3. 현위치 이동 함수
const curLocation = (map: any): Promise<number[]> => {
return new Promise((resolve, reject) => {
if (navigator.geolocation) {
// GeoLocation을 이용해서 접속 위치를 얻어옵니다
navigator.geolocation.getCurrentPosition(function (position) {
let lat = position.coords.latitude, // 위도
lng = position.coords.longitude; // 경도
let locPosition = new window.kakao.maps.LatLng(lat, lng);
map.panTo(locPosition);
resolve([lat, lng]);
});
} else {
let locPosition = new window.kakao.maps.LatLng(
33.450701,
126.570667,
);
map.setCenter(locPosition);
resolve([33.450701, 126.570667]);
}
});
};
export { curLocation };
프로미스를 사용해서 await 할 수 있도록 만들어 주었다. 왜냐하면 여기서 resolve된 좌표값을 가지고 주소로 변환해야 하기 때문이다.
4. 좌표 - 주소 변경 함수
const coordToAddress = async (lat: number, lng: number): Promise<string> => {
let geocoder = new window.kakao.maps.services.Geocoder();
return await new Promise((resolve) => {
geocoder.coord2Address(lng, lat, (result: any, status: any) => {
if (status === window.kakao.maps.services.Status.OK) {
let addr = !!result[0].road_address
? result[0].road_address.address_name
: result[0].address.address_name;
resolve(addr);
}
});
});
};
export { coordToAddress };
이전 현위치 이동 함수에서 반환한 위도, 경도 값을 가지고 주소로 변환한 코드다. 여기서는 주소를 반환해 준다.
4. 완성된 인스턴스
// 현위치로 이동 함수
const placeCurLocation = async () => {
if (map) {
marker.setMap(null); // 남아있는 마커 지우기
const [lat, lng] = await curLocation(map);
const addr = await coordToAddress(lat, lng); // 좌표 - 주소 변환
setAddress(addr);
}
};
완성된 코드다. 나는 현위치로 이동했을 때 마커를 지우고 싶어서 3번째 줄 처럼 작성했다.
'React' 카테고리의 다른 글
useInfiniteQuery를 활용한 무한 스크롤 구현하기 (2) | 2024.02.25 |
---|---|
React-query 사용해서 장바구니 상태 실시간 업데이트 하기 (0) | 2024.02.24 |
React - React Router 사용하기 (0) | 2023.05.19 |
React - React.memo (0) | 2023.05.18 |
React - useMemo (0) | 2023.05.18 |