본문 바로가기

React/MyDiary

React Google Map Marker 찍기

구현 목표

Google Map

위 이미지처럼 Google Maps API를 React 프로젝트에서 사용하여 사용자의 클릭 이벤트를 받아서 마커를 찍는 것을 목표로 한다.

 

구현 방법

구현 방법은 여러 가지가 있다. 공식적으로 지원하는 Google Maps API 문서를 참조하여 구현하는 방법이 대표적이다.

React는 좀 더 간편하게 구현할 수 있는 라이브러리를 지원한다. 물론 구글에서 공식적으로 지원하는 것은 아니다.

공식문서는 아니지만 개발자가 라이브러리 사용하는 방법을 서술한 문서를 지원한다.

 

React Google Maps Api Style Guide

 

react-google-maps-api-docs.netlify.app

 

@react-google-maps/api

React.js Google Maps API integration

www.npmjs.com

 

이 글에서는 라이브러리를 사용하여 구현한다.

가장 먼저 라이브러리를 프로젝트에 다운로드해줘야 한다.

 

npm i -S @react-google-maps/api

 

구현 코드

import React from "react";
import { GoogleMap, LoadScript, Marker } from "@react-google-maps/api";

const center = {
  lat: 37.54,
  lng: 127.04,
};

type props = {
  mapLocation: any | Array<Object>;
  containerStyle: object;
  mapviewMarkerClickHandler : Function
};

const Googlemap = ({ mapLocation, containerStyle, mapviewMarkerClickHandler }: props) => {

  return (
    <LoadScript
      googleMapsApiKey="??"
    >
      <GoogleMap
        mapContainerStyle={containerStyle}
        center={center}
        zoom={4}
      >
        {!Array.isArray(mapLocation) ? (
          <Marker position={mapLocation} />
        ) : (
          mapLocation.map((location: google.maps.LatLng, index: number) => (
            <Marker position={location} key={index} onClick={(e) => mapviewMarkerClickHandler(e)} />
          ))
        )}
      </GoogleMap>
    </LoadScript>
  );
};

export default React.memo(Googlemap);

 

위 JSX는 MyDiary 프로젝트에서 구글맵을 렌더링하고 mapLocation props의 타입에 따라 Marker를 렌더링 하는 컴포넌트이다.

위 코드들을 하나씩 분해해서 설명한다.

 

import { GoogleMap, LoadScript, Marker } from "@react-google-maps/api";

 

코드에서 가장 최상단에 위치한 import에는 구글맵에서 사용할 컴포넌트들이다.

여기서 LoadScript와 GoogleMap은 구글맵을 렌더링 하기 위해 필요한 가장 필수적인 컴포넌트들이다.

 

return (
    <LoadScript
      googleMapsApiKey="??"
    >
      <GoogleMap
        mapContainerStyle={containerStyle}
        center={center}
        zoom={4}
      >
        {!Array.isArray(mapLocation) ? (
          <Marker position={mapLocation} />
        ) : (
          mapLocation.map((location: google.maps.LatLng, index: number) => (
            <Marker position={location} key={index} onClick={(e) => mapviewMarkerClickHandler(e)} />
          ))
        )}
      </GoogleMap>
    </LoadScript>
  );

 

실제 렌더링을 수행하는 return 내부에는 위에서 import 해서 가져온 LoadScript와 GoogleMap, Marker 있다.

여기서 중요한 점은 LoadScript의 내부 엘리먼트를 GoogleMap으로 설정해야 한다는 것이다.

 

<LoadScript googleMapsApiKey="??"></LoadScript>

 

LoadScript에는 Google Map API 키 값과 같은 구글맵을 렌더링 하기 위해 가장 필수적인 데이터를 props로 받는다.

이러한 컴포넌트에서 받는 props들은 라이브러리 공식 문서에 서술되어 있으니 참고하면 된다.

Google Map API 생성은 https://developers.google.com/maps?hl=ko 에서 수행하면 된다.

 

<GoogleMap
        mapContainerStyle={containerStyle}
        center={center}
        zoom={4}
      >
</GoogleMap>

 

그리고 가장 중요한 GoogleMap 컴포넌트는 React에서 실제 구글맵을 렌더링 하는 역할을 수행한다.

구글맵에 각종 스타일이나 줌, 초기위치와 같은 구글맵에서 설정 가능한 무엇이든 props로 전달받는다.

하지만 구글맵 내부에서 찍는 Marker는 GoogleMap 컴포넌트 내부에서 선언되어야 한다.

 

{!Array.isArray(mapLocation) ? (
          <Marker position={mapLocation} />
        ) : (
          mapLocation.map((location: google.maps.LatLng, index: number) => (
            <Marker position={location} key={index} onClick={(e) => mapviewMarkerClickHandler(e)} />
          ))
        )}

 

실질적으로 Marker를 찍는 JSX이다.

Marker의 props 중 필수적으로 들어가야 할 position props는 Marker가 찍힐 위치이다.

google.maps.LatLng라는 구글맵의 특수한 타입으로 처리를 하는데 console.log로 출력할 때 각각 lat, lng라는 두 개의 키값을 갖는 Object 타입이다.

 

코드가 상당히 복잡하다. 이는 MyDiary 프로젝트에서 구글맵을 사용하는 페이지가 2개이기 때문이다.

첫 번째 페이지는 구글맵에서 onClick 이벤트로 찍는 위치에 Marker를 생성하도록 한다. 구글맵에서 onClick 이벤트를 발생시키고 그 이벤트를 console.log를 출력시켜보면 해당 위치의 latLng를 생성한다.

다시 말해 구글맵에서 마우스를 클릭할 때마다 클릭한 위치에 Marker를 찍히게 해야 한다.

 

두 번째 페이지는 DB에서 가져온 Marker의 위치들을 Array 타입으로 받아서 찍어준다. 즉, 여러 개의 Marker를 찍어야 한다.

그렇기 때문에 초기에 position props로 들어가는 것이 Array인지 Object인지 판별 후에 각각 다르게 처리를 해줘야 정상적으로 동작한다.

Array로 position을 받을 때

Array로 position을 전달받으면 DB에서 저장해둔 latLng를 가져와서 구글맵에 사용자가 볼 수 있도록 Marker를 찍어야 하는데 position은 google.maps.LatLng 타입만 받는다.

즉, Array에 있는 Object를 순차적으로 Marker로 넘겨주어 찍어주어야 한다.

그렇기 때문에 Array 판별 후 Array라고 판별 후에는 map() 함수를 사용하여 순차적으로 Array에 있는 LatLng를 Marker의 position으로 넘겨주어야 한다.

 

mapLocation.map((location: google.maps.LatLng, index: number) => (
            <Marker position={location} key={index} onClick={(e) => mapviewMarkerClickHandler(e)} />
          ))

 

또한 Array로 받은 경우에는 Marker의 onClick 이벤트를 추가했다. 클릭할 시 mapviewMarkerClickHandler(e)가 호출되도록 처리했고 대략 함수의 기능은 해당 위치와 일치하는 일기를 찾는 기능이다.

 

const mapviewMarkerClickHandler = (e: google.maps.MapMouseEvent) => {
    const latLng = {
      lat: e.latLng?.lat(),
      lng : e.latLng?.lng()
    };

    const result = diaryData.filter((diary) => {
      return JSON.stringify(latLng) === JSON.stringify(diary.location)
    })

    setlistData([...result])
  };

 

위 코드는 GoogleMap 컴포넌트를 호출한 페이지에서 처리한 선언된 함수이다. 이 함수는 GoogleMap 컴포넌트에서 props로 받도록 구현했다.

Marker를 클릭할시 filter() 함수를 통해서 Marker의 위치에 해당하는 diary를 반환한다. 그리고 반환된 diary를 list의 렌더링 되게 된다.