
이전 글에서 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>입니다!