이전 글에서 react-naver-maps
라이브러리의 마커 클러스터링 예제를 보며 마커 클러스터링 기능을 구현해 보았다.
하지만, 밑과 같은 이유로 예제를 내 프로젝트에 똑같이 적용할 수 있는 상황이 아니였다.
동적
으로 생성해주어야 한다. 어떻게 하면 React
환경에서 동적
으로 생성한 커스텀 마커들을 지도에 띄워줄 수 있을지 고민하였다.
react-naver-maps
라이브러리에서 클러스터링을 구현하는 방법예제는 같이 new naver.maps.Marker
을 이용해서 마커 객체를 생성하고,
생성한 마커 리스트를 cluster 객체에 넘겨 클러스터링을 구현한다.
즉, 생성된 마커 리스트를 cluster 객체에 넘겨주면 되는 것이다.
ref
를 사용하면 되지 않을까?new naver.maps.Marker
을 이용해서 생성한 마커들은 dom 객체로 생성이 된다.
그래서 동적으로 생성한 마커의 dom 객체를 ref에 저장하고 ref들의 리스트를 cluster 객체를 만들 때 사용하면 되지 않을까? 라고 생각해보고 실행에 옮겼다.
ref란? : render 메서드에서 생성된 DOM 노드나 React 엘리먼트에 접근하는 방법
React
의 useRef
hook을 이용하여 마커들의 ref
를 클러스터링 객체에 넘겨주는 방식으로 해결 할 수 있었다.
결론 : 생성한 마커의
ref
리스트를 이용해서 cluster 객체를 생성하자
icon으로 커스텀 되어있는 마커 컴포넌트가 있고,
마커 리스트에 따라 map에 마커들을 표시해 주는 코드가 있었다.
interface Props {
navermaps: typeof naver.maps;
item: string;
onClick: (item: string) => void;
}
function PreatMarker({ item, navermaps, onClick }: Props) {
return (
<NaverMarker
position={new navermaps.LatLng(item.latitude, item.longitude)}
icon={{
url: iconUrl,
scaledSize: new navermaps.Size(20, 20),
origin: new naver.maps.Point(0, 0),
}}
onClick={(e) => {
e.pointerEvent.stopPropagation();
onClick(item);
}}
/>
);
}
function Map() {
const navermaps = useNavermaps();
const [map, setMap] = useState<naver.maps.Map | null>(null);
const totalMakerList = ['marker1','marker2']
return (
<MapDiv css={mapCss}>
<NaverMap defaultCenter={center} defaultZoom={16} ref={setMap}>
<MarkerCluster markers={elRefs} markerInfo={totalMakerList} />
{totalMakerList.map((item, idx) => {
return (
<PreatMarker
key={item.id}
navermaps={navermaps}
item={item}
onClick={onMarkerClick}
/>
);
})}
</NaverMap>
</MapDiv>
)
}
마커 컴포넌트를 이용해서 화면에 마커들을 띄워주는 부분은 구현이 되어 있었다.
ref
리스트를 생성하기먼저 ref
를 리스트로 관리하기 위해
해당 링크를 참고하여 ref
리스트를 생성하였다.
const arrLength = arr.length;
const [elRefs, setElRefs] = React.useState([]);
React.useEffect(() => {
// add or remove refs
setElRefs((elRefs) =>
Array(arrLength)
.fill()
.map((_, i) => elRefs[i] || createRef()),
);
}, [arrLength]);
return (
<div>
{arr.map((el, i) => (
<div ref={elRefs[i]} style={...}>
...
</div>
))}
</div>
);
ref
에 저장하기마커 컴포넌트의 props로 ref
를 추가적으로 받도록 수정한다.
그리고 props로 받은 ref
를 연결한다.
interface Props {
navermaps: typeof naver.maps;
ref: any; // ref 추가
item: string;
onClick: (item: string) => void;
}
function PreatMarker({ item, navermaps, onClick }: Props) {
return (
<NaverMarker
ref={ref} // ref 추가
position={new navermaps.LatLng(item.latitude, item.longitude)}
icon={{
url: iconUrl,
scaledSize: new navermaps.Size(20, 20),
origin: new naver.maps.Point(0, 0),
}}
onClick={(e) => {
e.pointerEvent.stopPropagation();
onClick(item);
}}
/>
);
}
ref
를any
타입으로 선언하였는데,
any
를 쓰지 않고는 에러가 해결이 되지 않아서 어쩔 수 없는 선택이였습니다!
해결 방법을 아시는 분은 알려주세요 🙏
마커 컴포넌트를 사용하는 부분에서
마커의 개수 만큼 ref 리스트를 생성하고,
마커 컴포넌트의 ref
props에 해당하는 index의 ref
를 넘겨준다.
function Map() {
const navermaps = useNavermaps();
const [map, setMap] = useState<naver.maps.Map | null>(null);
const totalMakerList = ['marker1','marker2']
// *** 추가 ***
const [elRefs, setElRefs] = useState<RefObject<naver.maps.Marker>[]>([]);
useEffect(() => {
setElRefs((elRefs) =>
Array(arrLength)
.fill('')
.map((_, i) => elRefs[i] || createRef()),
);
}, [arrLength]);
return (
<MapDiv css={mapCss}>
<NaverMap defaultCenter={center} defaultZoom={16} ref={setMap}>
<MarkerCluster markers={elRefs} markerInfo={totalMakerList} />
{totalMakerList.map((item, idx) => {
return (
<PreatMarker
ref={elRefs[idx]} // *** 추가 ***
key={item.id}
navermaps={navermaps}
item={item}
onClick={onMarkerClick}
/>
);
})}
</NaverMap>
</MapDiv>
)
}
생성한 ref
리스트를 넘겨, 클러스터링 객체를 생성한다.
클러스터링 객체 이용해 Overlay
컴포넌트에 넘겨, 클러스터를 지도에 띄운다.
function MarkerCluster({
markers,
}: {
markers: RefObject<naver.maps.Marker>[];
}) {
const navermaps = useNavermaps();
const map = useMap();
const { htmlMarker1, htmlMarker2, htmlMarker3, htmlMarker4, htmlMarker5 } =
useGetClusterIcon(navermaps); // 클러스트 아이콘 dom 리스트
// https://github.com/zeakd/react-naver-maps/blob/main/website/src/samples/marker-cluster.js
const MarkerClustering = makeMarkerClustering(window.naver);
const getCluster = () => {
const markerList = markers.map((_marker) => {
return _marker.current;
});
const cluster = new MarkerClustering({
minClusterSize: 2,
maxZoom: 14, // 조절하면 클러스터링이 되는 기준이 달라짐 (map zoom level)
map: map,
markers: markerList.filter((marker) => marker),
disableClickZoom: false,
gridSize: 120,
icons: [htmlMarker1, htmlMarker2, htmlMarker3, htmlMarker4, htmlMarker5],
indexGenerator: [5, 10, 15, 20, 30],
stylingFunction: function (clusterMarker: any, count: number) {
clusterMarker.getElement().querySelector('div:first-child').innerText =
count;
},
});
return cluster;
}
// Customize Overlay 참고
// https://zeakd.github.io/react-naver-maps/guides/customize-overlays/
const [cluster, setCluster] = useState(getCluster());
useEffect(() => {
// 클러스트 객체 생성해서, 상태에 저장
setCluster(getCluster());
}, []);
return (
<Overlay element={{ ...cluster, setMap: () => null, getMap: () => null }} />
);
}
마커 클러스터링 조건에 해당하게 되면,
클러스터 되는 마커들은 보이지 않게 되고, 대신 클러스터 아이콘이 overlay 되어서 보이게 된다.
클러스터링 조건과, 클러스터 아이콘 등은 cluster 객체를 생성 할때 수정 할 수 있다.
클러스터링 적용 전 | 클러스터링 구현 후 |
---|---|
안녕하세요- Marker 의 ref 타입은
RefObject<naver.maps.Marker>
입니다!