Open yonghyeun opened 4 weeks ago
결국 생각나는 것은 Zoom Level 에 따라서 서버에서 받고자 하는 응답 데이터의 형태가 다르다는 점이다. 슈도 코드로 작성해보면 이런 식의 흐름은 어떨까 ?
만약 리액트 쿼리를 쓴다면 Context
는 QueryClient
가 될 것이다.
리액트 쿼리 프로바이더를 앱 최상단에 두기보다 GoogleMap
내부에만 선언해두면 어떨까 ? 캐싱되는 많은 데이터들을 메모리에 저장해두면 비효율적이라고 생각되기 때문이다.
혹은 어쩌면 캐싱이 꼭 필요할까 ? 싶기도 하다. 그냥 매번 디바운싱으로 현재 뷰포트 + ?? % 정도 되는 영역에 해당하는 Cluster , Marker
에 사용될 json
데이터들을 받으면 어떨까 ?
우선 가장 중요한 점은 google.maps.map
의 정보들을 이용해 virtual dom
이 reactive
해져야 하기 때문에 maps
이벤트 핸들러를 활용해주도록 하자
google.maps.map
에 부착 할 수 있는이벤트 핸들러들의 타입 선언은 다음과 같다.
export type MapEventProps = Partial<{
onBoundsChanged: (event: MapCameraChangedEvent) => void;
onCenterChanged: (event: MapCameraChangedEvent) => void;
onHeadingChanged: (event: MapCameraChangedEvent) => void;
onTiltChanged: (event: MapCameraChangedEvent) => void;
onZoomChanged: (event: MapCameraChangedEvent) => void;
onCameraChanged: (event: MapCameraChangedEvent) => void;
onClick: (event: MapMouseEvent) => void;
onDblclick: (event: MapMouseEvent) => void;
onContextmenu: (event: MapMouseEvent) => void;
onMousemove: (event: MapMouseEvent) => void;
onMouseover: (event: MapMouseEvent) => void;
onMouseout: (event: MapMouseEvent) => void;
onDrag: (event: MapEvent) => void;
onDragend: (event: MapEvent) => void;
onDragstart: (event: MapEvent) => void;
onTilesLoaded: (event: MapEvent) => void;
onIdle: (event: MapEvent) => void;
onProjectionChanged: (event: MapEvent) => void;
onIsFractionalZoomEnabledChanged: (event: MapEvent) => void;
onMapCapabilitiesChanged: (event: MapEvent) => void;
onMapTypeIdChanged: (event: MapEvent) => void;
onRenderingTypeChanged: (event: MapEvent) => void;
}>;
/**
* Sets up effects to bind event-handlers for all event-props in MapEventProps.
* @internal
*/
export declare function useMapEvents(map: google.maps.Map | null, props: MapEventProps): void;
export type MapEvent<T = unknown> = {
type: string;
map: google.maps.Map;
detail: T;
stoppable: boolean;
stop: () => void;
domEvent?: MouseEvent | TouchEvent | PointerEvent | KeyboardEvent | Event;
};
export type MapMouseEvent = MapEvent<{
latLng: google.maps.LatLngLiteral | null;
placeId: string | null;
}>;
export type MapCameraChangedEvent = MapEvent<{
center: google.maps.LatLngLiteral;
bounds: google.maps.LatLngBoundsLiteral;
zoom: number;
heading: number;
tilt: number;
}>;
이벤트 객체에서 map
에 접근하여 google.maps.map
에 존재하는 프로퍼티에 접근하여 현재 지도의 zoom ,center
등과 같은 정보를 가져오고 전역 상태로 저장하자
MapCameraChangedEvent
가 반환하는 이벤트 객체 , detail
에 지도에 관련된 정보들이 존재한다.
'use client';
import { Map, MapCameraChangedEvent } from '@vis.gl/react-google-maps';
import { NEXT_PUBLIC_GOOGLE_MAP_ID } from '@/shared/constant/env';
import { useMapStore } from '../hooks/store';
const MapGround = ({ children }: { children: React.ReactNode }) => {
const setCenter = useMapStore((state) => state.setCenter);
const setZoom = useMapStore((state) => state.setZoom);
const setBounds = useMapStore((state) => state.setBounds);
return (
<Map
mapId={NEXT_PUBLIC_GOOGLE_MAP_ID}
defaultCenter={{ lat: 37.553441, lng: 126.9696769 }}
defaultZoom={20}
onZoomChanged={(camera: MapCameraChangedEvent) => {
setZoom(camera.detail.zoom);
}}
onCameraChanged={(camera: MapCameraChangedEvent) => {
setCenter(camera.detail.center);
}}
onBoundsChanged={(camera: MapCameraChangedEvent) => {
setBounds(camera.detail.bounds);
}}
>
{children}
</Map>
);
};
export default MapGround;
다음과 같이 MapCameraChangedEvent
의 이벤트 핸들러를 이용하여 responsible
한 상태로 사용 할 수 있도록 하자
기존 클러스터링 방법의 문제점
현재 구글 맵스 API 에서 클러스터링을 할 때
js-markerclusters
라이브러리를 이용 할 것을 권장하고 있다.js-markerclusters
깃허브 : https://github.com/googlemaps/js-markerclusterer/tree/main현재 모든 코드를 이해 한 것은 아니지만 전체적인 작동 방식이 현재의 맵의
zoom
정도를 인수로 받아 인수로 받은marker
들을 클러스터링 시킨 마커로 변환하여 반환하는 것으로 보인다.만약
js-markercluster
라이브러리를 사용하면 발생 할 수 있을 것이라 생각되는 문제점은 두 가지이다.첫 번째론
js-markercluster
로 인해 클러스터링 된 마커의 인터페이스를 우리가 원하는데로 조작하는 것이 불가능해보인다는 점,두 번째론 클러스터링을 하기 위해선 결국 인수로 들어갈
marker
를 모두 받아와야 한다는 점이다.N
개의marker
정보를 모두 인수로 받은 후supercluster
에 존재하는 알고리즘 옵션을 통해marker -> cluster marker
로 변환하여 렌더링 하는 것인데 이 것이 과연 적절한 방식인가 ? 라는 물음이 든다.오히려 우리가 원하는 방식은
map
의zoom
정도에 따라 서로 다른api
응답값을 받아 렌더링 하기를 기대하고 있는 것인데 말이다.