구현하고자 하는 로직
DB에 저장된 위스키바 리스트가 서울시에 한정되어 있기 때문에, 다른 지역으로 넘어가면 지도에 별다른 리스트가 조회되지 않았다.
사용자 입장에서 생각해봤을 때 아래와 같은 로직을 추가하는 것이 유저 사용성이 더 편할 것 같다는 생각이 들어 아래의 로직을 추가했다.
1.서울시 전체 위스키바 크롤링하여 DB 저장, 해당 데이터를 보여주는 방식으로 로직 전면 변경
2. 약 350여개의 업장을 화면에 모두 출력하기보다 react-select를 이용해 '구'를 선택하도록 하여 구별로 업장 출력, 각 구마다 위스키바가 가장 많은 지점을 해당 구의 중앙 지점으로 설정
3. 사용자가 지도를 이동할 경우 idle 이벤트를 이용해 해당 구(또는 지역)을 감지하여 사용자가 이동한 구의 위스키바 데이터를 보여주거나 서울시가 아닐 경우 알림 메세지 출력
4. 지도를 이동하거나 select값을 변경했을 때, 다른 탭으로 이동하더라도 해당 세팅값을 기억할 수 있도록 recoil을 이용해 select 상태값 저장 및 관리
구현한 코드
✨ 위스키바 리스트를 보여주는 상위 페이지는 StoreList.jsx, 그 하위의 카카오맵 지도를 보여주는 컴포넌트는 KakaoMap.jsx 이다.
✨ 카카오맵 API 예시 코드는 JavaScript + HTML + CSS로만 코드가 구성되어 있어, React와 jsx 형식에 맞게 카카오맵 내부 함수를 수정한 ReactKakaoMapSDK 를 설치해 사용하였다.
KakaoMap.jsx 해당 코드 한 번에 보기
// * 지도의 중심 위치를 변경했을 경우 해당 구 위스키바 조회
const onIdleHandler = (map) => {
const latlng = map.getCenter();
geocoder.coord2Address(latlng.getLng(), latlng.getLat(), (address) => {
const getAddressName = address[0].address;
if (getAddressName.region_1depth_name === '서울') {
setAddressValue(getAddressName.region_2depth_name);
} else {
toast.error('현재 위스키바는 서울시 내 업장만 조회 가능합니다.');
}
});
};
return (
<MapSection>
<Map
center={{
lat: coords.lat,
lng: coords.lon,
}}
style={{
width: '100%',
height: '100vh',
}}
level={5}
onIdle={(map) => onIdleHandler(map)}
>
{markers &&
markers.map((marker) => (
<React.Fragment key={`marker_${marker.id}`}>
<MapMarker
position={marker.position}
image={{
src: mapPoint,
size: {
width: 27,
height: 27,
},
options: {
offset: {
x: 15,
y: -7,
},
},
}}
onClick={() => onMarkerClickHandler(marker.id)}
/>
{marker.isOverlayVisible && (
<CustomOverlayMap
value={marker.isOverlayVisible}
position={marker.position}
yAnchor={1}
onClick={() => onMarkerClickHandler(marker.id)}
>
<OverlayDiv onClick={() => onOverlayClickHandler(marker.id)}>
{marker.content}
<BiChevronRight />
</OverlayDiv>
</CustomOverlayMap>
)}
</React.Fragment>
))}
</Map>
</MapSection>
);
};
React KakaoMap SDK (카카오맵의 지도 출력 부분을 React + jsx 형식에 맞게 변환한 SDK) 에서 import해온 Map 컴포넌트에 onIdle을 걸어 사용자가 터치 또는 클릭으로 지도 출력 위치를 변경했을 경우 그 값을 감지할 수 있도록 한다.
그 후 map.getCenter()에서 getLng(), getLat()으로 변경된 위치의 위도, 경도를 꺼내고 geocoder를 이용해 해당 위도, 경도값을 한글 주소명으로 변환한다.
한글 주소로 변환한 첫 번째 depth값이 '서울'일 경우, 해당 지도의 위치가 변경되었어도 아직 서울 지역을 출력하고 있다는 말이기 때문에 2 depth인 해당 '지역구'를 상위 컴포넌트로 넘기기 위해 Recoil에 값을 저장시킨다.
만약 한글 주소로 변환한 2 depth값이 '서울'이 아닐 경우 사용자가 이동시킨 지도 값이 서울 외의 지역을 출력하고 있기 때문에 error alert를 띄워 현재 위스키바는 서울시 내 업장만 조회 가능함을 알린다.
StoreList.jsx 해당 코드 한 번에 보기
// StoreList.jsx
// * [위치 동의] 사용자 위치정보 허용했을 경우
const approve = (position) => {
const { kakao } = window;
const geocoder = new kakao.maps.services.Geocoder();
geocoder.coord2Address(position.coords.longitude, position.coords.latitude, (address) => {
// 사용자의 위치가 서울시가 아닐 경우 default 강남구로 설정
const getAddressName = address[0];
if (getAddressName.region_1depth_name !== '서울') {
setSelectStatus(statusOptions[getMapValue]);
setCoords({
lat: statusOptions[getMapValue].value.lat,
lon: statusOptions[getMapValue].value.lon,
});
} else {
const userLoacation = statusOptions.find((option) => option.label === getAddressName.region_2depth_name);
setSelectStatus(userLoacation);
setCoords({
lat: position.coords.latitude,
lon: position.coords.longitude,
});
}
});
};
// * [위치 동의] 사용자 위치정보 거절하거나 오류일 경우 default 위도/경도 설정
const reject = () => {
setCoords({
lat: statusOptions[getMapValue].value.lat,
lon: statusOptions[getMapValue].value.lon,
});
};
// * [스토어 리스트] 위스키바 목록 보기 toggle
const isShowListHandler = () => setNearbyToggle(!nearbyToggle);
// * [스토어 리스트] 조회
const { refetch } = useQuery('getStoreList', () => getStoreList(selectStatus.label), {
onSuccess: (response) => {
setStoreList(response);
setCoords({ lat: selectStatus.value.lat, lon: selectStatus.value.lon });
},
});
// * [스토어 리스트] 지도 이동해 구가 바뀔때마다 재조회
useEffect(() => {
if (getAddressValue) {
const addressIdx = statusOptions.findIndex((i) => i.label === getAddressValue);
setMapValue(addressIdx);
setSelectStatus(statusOptions[addressIdx]);
toast.success(`중심 위치를 ${getAddressValue}로 변경합니다.`);
}
}, [getAddressValue]);
// * [스토어 리스트] select의 값이 바뀔때마다 재조회
useEffect(() => {
setMapValue(statusOptions.findIndex((i) => i.label === selectStatus.label));
refetch();
}, [selectStatus, currentLocationToggle]);
// KakaoMap.jsx
// * 지도의 중심 위치를 변경했을 경우 해당 구 위스키바 조회
const onIdleHandler = (map) => {
const latlng = map.getCenter();
geocoder.coord2Address(latlng.getLng(), latlng.getLat(), (address) => {
const getAddressName = address[0].address;
if (getAddressName.region_1depth_name === '서울') {
setAddressValue(getAddressName.region_2depth_name);
} else {
toast.error('현재 위스키바는 서울시 내 업장만 조회 가능합니다.');
}
});
};
사용자가 카카오맵 출력 위치를 변경했거나, 서울시 지역구가 출력되고 있는 select의 값을 바뀌는 것을 각각의 useEffect로 설정해 바로 변경 함수가 실행될 수 있도록 한다.
이때, select로 지역구를 선택했을 때 보일 중심 위치는 지역구를 하나하나 찍어보면서 해당 지역구에서 위스키바가 제일 많은 쪽의 중간 지점의 lat, lan값을 추출해 하드코딩으로 관리하고 있었다. 따라서 지도를 끌어서 이동했을 경우에도 위스키바가 제일 많은 밀집 지역(설정해 둔 해당 지역구의 중심 위치)으로 바로 이동할 수 있도록 하며 중심위치를 'XX구'로 변경한다는 알림을 띄워주었다.
[해당 코드 보러 가기]
https://github.com/dawhisky/dawhisky-FE/blob/develop/src/pages/StoreList.jsx
https://github.com/dawhisky/dawhisky-FE/blob/develop/src/pages/KakaoMap.jsx
[참고 자료]
https://react-kakao-maps-sdk.jaeseokim.dev/
https://apis.map.kakao.com/web/sample/
'Front-End > React' 카테고리의 다른 글
[React] 카카오맵(3) 맵 마커 비동기 출력 문제 해결하기 (0) | 2023.06.14 |
---|---|
[React] React-router-dom을 이용한 Private Route 설정 시 인증/인가 처리 오류 (0) | 2023.06.03 |
[React] 카카오맵(1) 지도 내장 검색 기능 사용해 리스트 출력하기 (0) | 2023.06.01 |
[React] 디바운싱을 이용한 추천 검색어, 인기 검색어, 키워드 검색 기능 구현하기 (0) | 2023.05.31 |
[React] 리액트에서 Styled Components Global Style, Theme 설정하기 (0) | 2023.05.30 |