Next.js + typeScript 에서 Leaflet 사용하기

support·2022년 7월 25일
2

Today I Learned

목록 보기
5/11

1. 설치

npm install react react-dom leaflet
npm install react-leaflet
npm install -D @types/leaflet
import { MapContainer, TileLayer, Marker, Popup } from "react-leaflet";

export default function Map() {
  return (
    <MapContainer center={[51.505, -0.09]} zoom={13} scrollWheelZoom={false}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[51.505, -0.09]}>
        <Popup>
          A pretty CSS3 popup. <br /> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  );
}

홈페이지에서 알려준 방법대로 설치와 import를 하면 Window is not defined 라는 에러가 뜬다
window는 client-side에만 존재하고 Next.js 에서는 SSR을 지원하기 때문에 처음 웹 페이지를 렌더링 할 때 window나 document 전역객체를 사용할 수 없다.

해결하기 위해서 Next.js에서 dynamic 이라는 함수를 제공한다.

2. 시도

Next.js Leaflet Starter

위의 홈페이지에 들어가면 Next.js에서 Leaflet을 사용해 띄워놓은 페이지와 코드를 볼 수 있다.

사용 방법
1. Map 컴포넌트를 작성하고
2. index 에서 dynamic import를 한 뒤
3. 필요한 곳에서 index.js 를 import 해 사용한다.


지도의 크기를 지정해주고 css 까지 import 해주면 지도를 불러올 수 있다.

import "leaflet/dist/leaflet.css"; ✅ 추가
...
return(
    <MapContainer
      center={[51.505, -0.09]}
      zoom={13}
      scrollWheelZoom={true}
      style={{ width: "100%", height: "100vh" }}  추가
    >
    </MapContainer>
)

3. 해결

이 방법으로 지도를 띄우고 마커를 표시하는 데는 성공했지만
주소 검색 시 위치 변경을 해주는 곳에서 props가 변경 되었을 때 컴포넌트가 재 렌더링 되지 못해 기능 구현이 되지 않았다.

위의 방법 처럼 원래 index를 import 해오던 것을 참조해서 가져오지 않고 컴포넌트 내에서 바로 import를 해서 해결했다.

import dynamic from "next/dynamic";

export default function IndexPage() {
  const MapWithNoSSR = dynamic(() => import("./Map"), {
    ssr: false
  });

  return (
    <>
      <MapWithNoSSR />
    </>
  );
}

4. 재사용 + Marker

Map을 필요한 곳에서 기능을 추가해서 사용해야 하기 때문에 Map안에 children prop을 사용한다.
children을 사용해서 Marker 기능을 추가해보자.

ProjectTable.tsx

import dynamic from "next/dynamic";
import { useRecoilValue } from "recoil";
import { location } from "../shared/state/atoms";

const ProjectTable = () => {
  const getLocation = useRecoilValue(location);

 // ✅ dynamic import
  const MapWithNoSSR = dynamic(() => import("../shared/components/Map"), {
    ssr: false,
  });

  const MapMarkerNoSSR = dynamic(
    () => import("../shared/components/MapMarker"), {
    ssr: false,
  });

  return (
    <>
      <MapWithNoSSR getLocation={getLocation}>
        <MapMarkerNoSSR />
      </MapWithNoSSR>
      <Search />
    </>
  );
};

Map.tsx

import React from "react";
import "leaflet/dist/leaflet.css";
import { MapContainer, TileLayer } from "react-leaflet";

interface MapProps {
  children?: React.ReactNode;
  getLocation: { x: number; y: number };
}

const Map = ({ getLocation, children }: MapProps) => {
  return (
    <MapContainer
      center={[getLocation.x, getLocation.y]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "100%", height: "550px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
       {children} // ✅ check
    </MapContainer>
  );
};

export default Map;

MapMarker.tsx

import { useRecoilValue } from "recoil";
import { Marker } from "react-leaflet";
import L from "leaflet";

const MapMarker = () => {
  const getProjectList = useRecoilValue(projectList);
  const [mapMarker, setMapMarker] = useState<ReactElement | null>(null);
  // ✅ Marker를 state에 넣어서 return

  const getDynamicMarker = () => {
    const blueIcon = L.icon({
      iconUrl: "/blueIcon.svg",
      iconSize: [30, 90],
    });

    setMapMarker(
      <div>
        {getProjectList?.data?.map(
          (item: { lat: number; lon: number }, id: number) => (
            <Marker
              key={id}
              position={[item.lat, item.lon]}
              icon={blueIcon}
            ></Marker>
          )
        )}
      </div>
    );
  };

  useEffect(() => {
    getDynamicMarker();
  }, [getProjectList]);

  return mapMarker;
};

export default MapMarker;

참고
React leaflet not rendering properly - 지도가 타일처럼 깨져서 나올 때
codeSandBox example - 지도 완성 샘플

0개의 댓글