JaeSeoKim / react-kakao-maps-sdk

React components for using kakao map api
https://react-kakao-maps-sdk.jaeseokim.dev
MIT License
266 stars 29 forks source link

onClustered 에서 setContent가 작동하지 않는 문제 #52

Closed igoman2 closed 11 months ago

igoman2 commented 11 months ago

onClusterover, onClusterclick 과 같은 콜백에서는 매개변수로 받은 cluster 객체로부터 cluster.getClusterMarker().setContent() 를 통해 클러스터의 html, css를 조작할 수 있는데 onClustered 콜백의 매개변수인 clusters 내에 존재하는 cluster로 동일한 코드를 작성하면 적용이 되지 않고 디폴트 클러스터의 ui가 그려지게 됩니다.

// 클릭 이벤트 시 개별 클러스터 요소 커스텀 적용됨
  const onClusterClick = (
    target: kakao.maps.MarkerClusterer, cluster: kakao.maps.Cluster
  ) => {
    cluster.getClusterMarker().setContent('테스트String');
  };
// 렌더링시 개별 클러스터 요소 커스텀 적용되지 않음
 onClustered={(target: kakao.maps.MarkerClusterer, clusters: kakao.maps.Cluster[]) => {
    clusters.map((cluster: kakao.maps.Cluster) => {
      cluster.getClusterMarker().setContent('테스트String');
    });
  }}

저는 onClustered 에서 받는 clusters 객체가 각 클러스터의 집합을 의미해서 이것을 순회하면 각 클러스터의 HTML 을 제어할 수 있을거라 예상했는데 이게 아닐까요? 클릭이나 마우스호버와 같은 이벤트 없이 렌더링 초기에 클러스터 내에 마커들로부터 얻은 정보들을 화면에 보여주고 싶은데, 확인해주시면 감사하겠습니다!

JaeSeoKim commented 11 months ago

@igoman2 안녕하세요. 현재 군대에 입영을 한 상황이라, 답변이 제한될 수 있음을 미리 알려드립니다.! (CodeSandBox와 같은 Web IDE 서비스를 이용하여 해당 이슈를 구현해주시면 이슈를 확인하는데 도움이 됩니다.!)

해당 문제를 확인해보니, 이벤트가 정상적으로 동작하지 않는 것을 확인하였습니다. 여러가지 테스트를 해봐야겠지만, 내부코드에서 이벤트 등록시기를 앞으로 당기는 형태로 수정 하면 해당문제점이 해결이 될 것 같습니다. https://github.com/JaeSeoKim/react-kakao-maps-sdk/blob/main/src/components/MarkerClusterer.tsx#L233C59-L233C59

핸드폰으로 작성한 코드라 오류가 있을 수 있습니다. 의사코드로 이해해주시길 바랍니다.

const Test = () => {
  const [markerClusterer, setMarkerClusterer] = useState(null) // ref 사용 금지, 생성된 이벤트를 가지고 effect 발생시키기 위함.

  useEffect(() => {
    if (markerClusterer) {
     const callback = (clusters) => {console.log(clusters}
     markerClusterer.addListener(“clustered”, callback ); // 내부 API 테스트 필요
     return () => markerClusterer.removeListener(“clustered”, callback);
    } 
  }, [markerClusterer])

  return <MarkerClusterer ref={setMarkerClusterer}>
  // marker 객체 들…
  </MarkerClusterer>
}

위와 같은 코드를 작성한 경우 제대로 이벤트가 발생하는 것을 확인할 수 있습니다. 다만, 내부적으로 Clusterer 객체에게 marker 객체를 전달하는 방법이 React Component의 자식으로 들어온 경우 추가하는 것 이다 보니, 한번에 marker들을 전달하지 않고, children를 순회하며, 렌더링 될 때 추가되는 방식으로 동작하고 있습니다. 따라서, 해당 이벤트가 n개의 Maker 객체만큼 발생합니다.

해당 문제점을 해결하는 구조를 고민해보겠습니다. 일단 위와 같이 임시로 사용하는 경우 Debounce 적용하여, 이벤트 동작을 최소화 하는 작업이 필요할 것 같습니다.

추가로, React에서 DOM를 직접 건드는 경우 SideEffect를 발생하는 행위므로, React Dom Tree가 깨질 수 있습니다. 따라서, 직접 html로 제어하는 것 보다, 제공하는 Component를 이용하여 제어하는 것을 추천드립니다.!

https://react-kakao-maps-sdk.jaeseokim.dev/docs/sample/library/chickenClusterer 최대한 이와 같은 방법으로 구현을 하고, 이 기능으로 부족한 부분이 있다면, 라이브러리에서 대응할 수 있는 방법이 무엇이 있을지 고민해보겠습니다..!

igoman2 commented 11 months ago

ref 를 통해 effect를 발생 시키는 위 방식으로 onClustered 관련해서는 우회가 가능하지만 이렇게 되면 아래 두 가지 경우 모두 onClusterclick, onClusterover 과 같은 다른 핸들러가 등록되지 않는 것 같습니다.

<MarkerClusterer
            ref={setMarkerClusterer}
            onClusterclick={onClusterClick}
            onClusterover={onClusterOver}
          >
</MarkerClusterer>
  useEffect(() => {
    if (markerClusterer) {
      const onClustered = (clusters: kakao.maps.Cluster[]) => {}

      const onClusterClick = () => {};

      markerClusterer.addListener("clusterclick", onClusterClick);
      markerClusterer.addListener("clustered", onClustered); // 내부 API 테스트 필요

      return () => {
        markerClusterer.removeListener("clustered", onClustered);
        markerClusterer.removeListener("clusterclick", onClusterClick);
      };
    }
  }, [markerClusterer]);

effect 내부의 onClustered 콜백 안에서 cluster를 순회하며 setContent를 해주어 클러스터 돔 인스턴스가 초기화 되면서 click이나 over 이벤트 핸들러가 사라지는 문제였네요. 다만 MarkerClusterer 내부에 CustomOverlayMap 컴포넌트가 있는 경우 CustomOverlayMap의 onCreate와 ref로 잡은 markerClusterer 간 데이터 불일치가 있네요. CustomOverlayMap의 onCreate에서 marker에 대해 객체 확장(마커에게 고유 id를 부여하였습니다)을 하였지만 effect에서 cluster를 통해 marker를 조회하는 경우 일부 마커 요소에 id가 없네요.(effect로 회피하면서 생기는 시점 문제인 듯 싶습니다.)

결론적으로 위 방법도 완전하게 onClustered 시점에 마커를 제어할 수 없습니다. 현재 라이브러리에서 지원하는 (https://react-kakao-maps-sdk.jaeseokim.dev/docs/sample/library/chickenClusterer) 클러스터러 컴포넌트로는 클러스터 시 마커의 정보를 파악하여 클러스터의 html 을 수정하고 싶은 경우 위와 같이 회피를 하지 않으면 불가능한 기능입니다. (아마 수요가 꽤 있는 기능일 것 같습니다! 클러스터 내부 마커에 존재하는 데이터들로 클러스터에 정보를 나타내고 싶은 경우..)

또한 위 이슈와 더불어 첫 렌더링 시에 onClustered 이벤트가 랜덤적으로 발생하지 않는 문제도 있습니다. 위에 말씀하신 것 처럼 맵이 DOM에 그려지는 시점과 이벤트가 붙는 시점 차이 문제 일 것 같습니다(?) 테스트 결과 drag, zoom 이벤트 등 리렌더링 되면서 클러스터링이 다시 발생하는 경우 항상 onClustered 이벤트가 발생하는 것이 보장되는 것으로 보입니다. 문제는 처음 지도가 렌더링 되고 클러스터링이 발생할 때 onClustered가 발생하지 않는데 패턴은 다음과 같습니다. 1) 다른 화면에 있다가 지도뷰로 라우팅 되어서 지도가 렌더링 되는 경우에는 onClustered 이벤트 발생 2) 동일한 화면에서 새로고침을 해서 다시 렌더링 되는 경우 높은 확률로 onClustered 이벤트 발생하지 않음

JaeSeoKim commented 11 months ago

@igoman2 해당 문제점에 대해서 확인해본 결과 N개의 marker가 추가 될 때 마다 onClustered 이벤트가 발생하여, 정상적으로 동작하지 않는 것으로 판단됩니다. 그래서 내부 라이브러리 코드를 수정하여, addMarker 를 진행할 때 reDraw를 발생하지 않도록 하고, children이 모두 렌더링 된 이후 redraw를 직접 호출하게 하는 것으로 문제점을 해결하였습니다.

또한 처음에 답변 들였던 setContent 함수를 사용하는 영역은 생각해보니 React에서 관리하는 Dom Tree가 아니라 문제가 없을 것 같습니다.!

일단 간단하게 코드를 수정하여 베타로 배포하였는데, 사용해보시고 피드백 주시면 감사하겠습니다..! https://www.npmjs.com/package/react-kakao-maps-sdk/v/1.1.10-beta

JaeSeoKim commented 11 months ago

@igoman2 v1.1.11 으로 배포 완료 하였습니다. 추가적으로 문제가 발생하거나 더 이야기 나눌 부분이 있다면 Issue를 Open하여 더 이야기 해주세요.!