React에서 카카오 지도 API 활용하기

깡스·2023년 7월 27일
0

이번 나의 산행 기록을 관리하는 프로젝트에서 지도는 필수적으로 요구되는 기능이기에, kakao에서 제공하는 지도 API를 활용해 사용하는 과정을 기록합니다.

Typescript

시작하기에 앞서 저는 프로젝트를 typescript로 진행하였으며, 카카오에서는 공식적으로 지도 API의 type을 지원하고 있지 않기에 링크의 오픈소스를 사용하였습니다.

React에서 활용하기

먼저 기본적인 사용법은 카카오 지도의 Docs에 샘플과 함께 상세히 설명되어 있습니다.

기본적인 Map의 샘플코드는 아래와 같습니다.

var container = document.getElementById('map'),
    options = {
         center: new kakao.maps.LatLng(33.450701, 126.570667),
         level: 3
    };
 
var map = new kakao.maps.Map(container, options);

위 코드의 경우 React가 아닌 바닐라 자바스크립트에서 활용하는 방법이기에 React에서 사용하기 적합하지 않았습니다.

<Map center={{ lat: 30, lng: 30 }} level={3}>
	<Marker position={{ lat:30, lng: 30 }}/>
  
  	<Polyline paths={[[x, y], [x, y]]}/>
</Map>

위와 같이 컴포넌트 형태로 사용할 수 있으며, center, level과 같은 지도의 속성들을 Marker, Polyline와 같은 하위 컴포넌트에서 컨트롤할 수 있으면 좋겠다고 생각하였습니다.

Map

const MapContext = createContext<MapContextValue | null>(null);

export const Map = ({ children, ...props }: MapProps & PropsWithChildren) => {
  const [map, setMap] = useState<kakao.maps.Map | null>(null);
  
  const [options, _setOptions] = useState<MapProps>({
    ...props,
    smooth: false
  });

  const containerRef = useRef<HTMLDivElement>(null);

  // 복수의 옵션 변경시 사용
  const setOption: MapContextController["setOption"] = useCallback(updateOptions => {
    _setOptions(prev => ({ ...prev, ...updateOptions }));
  }, []);

  // ... 생략

  // 최초 지도 렌더링
  useMountLayout(() => {
    if (containerRef.current) {
      const _map = new kakao.maps.Map(containerRef.current, {
        ...props,
        center: toCoords(options.center)
      });

      setMap(_map);
    }
  });

  const values = useMemo(
    () => ({
      map: map as kakao.maps.Map,
      controller: {
        setOption,
      }
    }),
    [map, setCenter, setOption, setSmooth]
  );

  return (
    <MapContext.Provider value={values}>
      <SizeBox ref={containerRef} fullScreen>
        {map && children}
      </SizeBox>
    </MapContext.Provider>
  );
};

export const useMap = () => {
  const mapContext = useContext(MapContext);

  if (!mapContext) {
    throw new Error("useMap is only available within Map");
  }

  return mapContext;
};

하위 컴포넌트에서 Map의 옵션을 컨트롤 하기 위한 방법으로 context를 사용해주었습니다.

옵션을 컨트롤할 수 있는 controllercontext를 통해 공급해주어 useMap훅을 통해서 접근할 수 있게 되었습니다.

하위 컴포넌트에서 활용

// App.tsx
const App = () => {
	return (
      <Map center={{lat: 30, lng: 120}} smooth={false}>
    	<MountainMap/>
      </Map>
    )
}

위와 같은 구조로 마크업을 구성할 경우

// MountainMap.tsx
const MountainMap = () => {
  const { controller } = useMap();
  
  const handleMarkerClick = () => {
    controller.setOption({center: { lat: 31, lng: 122 }, smooth: true })
  }  
  
  return <>
  	<Marker onClick={handleMarkerClick}/>  
  </>
}

하위 컴포넌트인 MountainMap에서 controller를 통해 Mapoption에 관여할 수 있게 됩니다.

// MountainMap.tsx
const MountainMap = () => {
  const { map, controller } = useMap();
  
  const handleMarkerClick = () => {
    controller.setOption({center: { lat: 31, lng: 122 }, smooth: true })
  }  
  
  useEffect(() => {
    const handleMapClick = () => {
      console.log("map click")
    }

    kakao.maps.event.addListener(map, 'click', handleMapClick)

    return () => {
      kakao.maps.event.removeListener(map, 'click', handleMapClick)
    }
  },[])
  
  return <>
  	<Marker onClick={handleMarkerClick}/>  
  </>
}

useMap에서는 target이되는 map도 반환하고 있기에 하위 컴포넌트에서 Map에 이벤트 리스너를 부착하는 행위도 가능해집니다.

마무리

카카오 지도의 경우 이미 React에서 사용하기 편리하게끔 제공하는 라이브러리가 존재하나, 이런 바닐라 자바스크립트 환경에 맞춰 제공되는 API를 React환경에 맞춰 변환해보는 과정을 경험해보고 싶었기에 라이브러리 없어 직접 진행해 보았습니다.

0개의 댓글

관련 채용 정보