React로 MapBox 맵을 표시합니다. 🗺️

이 게시물의 목적은 MapBox GL JS 라이브러리를 사용하여 React JS 애플리케이션에서 대화형 지도를 표시하는 방법을 가르치는 것입니다.

이 경우 지도를 표시하고 더블 클릭한 순간에 실행되는 이벤트를 추가할 것입니다. 방금 더블 클릭한 위치에 마커가 배치됩니다.

🚨 Note: This post requires you to know the basics of React with TypeScript (basic hooks and fetch requests).



모든 종류의 피드백이나 개선 사항을 환영합니다. 감사합니다. 기사를 즐기시기 바랍니다. 🤗


목차.



📌 Technologies to be used...

📌 Before you start coding...

📌 Creating the project.

📌 First steps.

📌 Creating the component to display the map.

📌 Showing the map on screen.

📍 Keeping the reference to the map container.

🔴 Why do we need to keep the reference?



📍 Initializing MapBox.


📌 Adding a marker at the initial position.

📍 Before our component grows.

📍 Listening for the 'load' event in the map.

🔴 Creating the function to add markers.



📍 Showing the marker.


📌 Adding a new marker on the map when double clicked.
📌 Conclusion.
📌 Source code.

🧵 사용할 기술.

  • ▶️ React JS (v.18)
  • ▶️ Vite JS
  • ▶️ TypeScript
  • ▶️ MapBox
  • ▶️ CSS (이 게시물 끝에 있는 저장소에서 스타일을 찾을 수 있습니다)

  • 🧵 코딩을 시작하기 전에 ...

    Before we start working with the code we have to do a couple of things to be able to use the MapBox map.

    1- You have to create a MapBox account.

    2- In your account you will look for the access token that MapBox creates by default or if you prefer, you can create a new access token.
    3- Save this access token to use it later.

     

    🧵 지도를 표시할 구성 요소를 만듭니다.

    We will name the project: show-mapbox (optional, you can name it whatever you like).

    npm init vite@latest
    

    We create the project with Vite JS and select React with TypeScript.

    Then we execute the following command to navigate to the directory just created.

    cd show-mapbox
    

    Then we install the dependencies.

    npm install
    

    Then we open the project in a code editor (in my case VS code).

    code .
    

     

    🧵 첫 번째 단계.

    We need to install MapBox in our application:

    npm i mapbox-gl
    

    And since we are using TypeScript, we need to install the MapBox types:

    npm i -D @types/mapbox-gl
    

    Inside the folder src/App.tsx we delete all the content of the file and place a h1 that says "Hello world " in the meantime.

    const App = () => {
      return (
        <div>
            <h1>Hello World</h1>
        </div>
      )
    }
    export default App
    

    🚨 Note: It is necessary to place the MapBox styles so that when we are using the map, it looks the best way.

    The best will be to import the styles in the src/main.tsx file, at the top of our application.
    Line to place:

    import 'mapbox-gl/dist/mapbox-gl.css'
    

    This is what the src/main.tsx file would look like

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    
    import App from './App';
    
    import 'mapbox-gl/dist/mapbox-gl.css';
    import './index.css';
    
    ReactDOM.createRoot(document.getElementById('root')!).render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    )
    

     

    🧵 지도를 표시할 구성 요소를 만듭니다.

    We create the src/components folder and create the MapView.tsx file.
    And the only thing we need to display the map is a div tag.

    🚨 Note: Before we start displaying the map; this component must be given styles, a height and a width so that we can then display the map correctly.

    export const MapView = () => {
        return (
            <div className='map' />
        )
    }
    

     

    🧵 화면에 지도를 표시합니다.

    To display the map we will need to use 2 hooks.
    The first one will be the useRef. We need useRef to store the reference of the div where the map will be rendered.

    The other hook is the useEffect hook. We will use this hook to initialize the map.

     

    🟠 맵 컨테이너에 대한 참조를 유지합니다.

    We use the hook useRef for this task, as follows:

    import { useRef } from 'react';
    
    export const MapView = () => {
    
        const mapRef = useRef<HTMLDivElement>(null);
    
        return <div ref={mapRef} className='map' />
    }
    

     

    🔴 참조를 유지해야 하는 이유는 무엇입니까?

    Bueno, podríamos solo colocar solo un ID al div y ya con eso funcionaria. 😌

    El problema sera cuando queramos usar mas de un mapa. 🤔

    Si usamos más de un componente MapView, solo se renderizaría un solo mapa por que tienen el mismo ID; y para evitar eso, usamos el hook useRef, ya que cada vez que reutilizamos el componente MapView se creara una nueva referencia.

     

    🟠 MapBox 초기화.

    We create the src/utils folder and create a new file called initMap.ts and there we will build the function to initialize the map.

    This function has to receive:

    • container: HTML element, in this case the div, where the map will be rendered.

    • coords: coordinates of the place. They have to be of type array of two numbers, where the first position is the longitude and the second position is the latitude.

    import { Map } from 'mapbox-gl';
    
    export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
    }
    

    Inside the function we are going to return a new instance of Map.

    We return it because we are going to need that instance to make more events and actions. In the case that you only need to show the map and already, it won't be necessary to return anything.

    import { Map } from 'mapbox-gl';
    
    export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
        return new Map();
    }
    

    The Map class requires certain options.

  • container: the HTML element where the map will be rendered, its value will be the container that comes to us by parameter of the function.

  • style: type of style of the map, in this case I will use the dark, in the MapBox documentation there are more styles .
  • pitchWithRotate: 지도의 기울기 컨트롤입니다. 이 경우에는 제거하고 싶으므로 false를 넣습니다.
  • center: 초기화 시 지도가 위치할 좌표이며, 그 값은 함수의 매개변수에 의해 우리에게 오는 좌표가 됩니다.
  • 확대/축소: 지도의 초기 확대/축소 수준은 0에서 22까지입니다.
  • accessToken: 이전에 저장한 토큰입니다. 따라서 이 토큰을 환경 변수에 저장하고 이 accessToken 속성에서 이 변수를 사용하는 것이 좋습니다.
  • doubleClickZoom: 기본적으로 두 번 클릭할 때 트리거되는 동작은 확대/축소를 늘리는 것이지만 다른 작업에 두 번 클릭 동작을 사용할 것이므로 false로 설정합니다.

  • 그리고 그것은 사용할 준비가 된 우리의 기능이 될 것입니다. 😌

    import { Map } from 'mapbox-gl';
    
    export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
    
        return new Map({
            container,
            style: 'mapbox://styles/mapbox/dark-v10',
            pitchWithRotate: false,
            center: coords,
            zoom: 15,
            accessToken: import.meta.env.VITE_KEY as string,
            doubleClickZoom: false
        });
    
    }
    

    이제 MapView 구성 요소에서 useEffect를 사용하여 생성한 함수를 호출합니다.

    useEffect 내부에서 useRef 값이 존재하는 경우에만 맵을 초기화하는 조건을 만들 것입니다.

    initMap 함수에서 mapRef의 현재 속성에 있는 HTML 요소를 전송합니다.
    그런 다음 좌표( [경도, 위도] )를 보냅니다.

    import { useRef } from 'react';;
    import { useMap } from '../hook/useMap';
    
    export const MapView = () => {
    
        const mapRef = useRef<HTMLDivElement>(null);
    
        useEffect(() => {
            if (mapRef.current) {
                initMap(
                    mapRef.current,
                    [-100.31019063199852, 25.66901932031443]
                )
            }
        }, []);
    
        return (
            <div ref={mapRef} className='map' />
        )
    }
    

    이제 이 이미지와 같이 화면 🥳에 지도가 표시됩니다.



    글쎄, 이제 어떡해?
    북마크를 추가할 수 있는 이벤트를 추가하면 어떨까요 😉.

    🧵 초기 위치에 마커 추가.

    Before creating events with the map, we have to keep the reference to the Map instance, for that we will use again useRef.

    We create a new reference called mapInitRef which will be of type map or null.

    The initMap function returns the Map instance, so we will assign this instance to mapInitRef.

    const mapInitRef = useRef<Map | null>(null);
    
    useEffect(() => {
        if (mapRef.current) {
    
            mapInitRef.current = initMap(
                mapRef.current,
                [-100.31019063199852, 25.66901932031443]
            );
    
        }
    }, []);
    

     

    🟠 구성 요소가 커지기 전에...

    At this point, it will be better to refactor our code, creating a custom hook to handle the map logic and leave our MapView component clean.

    We create the src/hooks folder and inside we create the useMap.ts file and move the MapView logic to the useMap.ts file.

    This custom hook receives as parameter the container where the map will be rendered.

    Now, we replace the word mapRef by container.

    import { useEffect, useRef } from 'react';
    import { Map } from 'mapbox-gl';
    import { initMap } from '../utils/initMap';
    
    export const useMap = (container: React.RefObject<HTMLDivElement>) => {
    
        const mapInitRef = useRef<Map | null>(null);
    
        useEffect(() => {
            if (container.current) {
    
                mapInitRef.current = initMap(
                    container.current,
                    [-100.31019063199852, 25.66901932031443]
                );
    
            }
        }, []);
    }
    

    Then we make the call of the hook in our component MapView.

    And thus we will have our component, much more readable. 😉

    import { useRef } from 'react';;
    import { useMap } from '../hook/useMap';
    
    export const MapView = () => {
    
        const mapRef = useRef<HTMLDivElement>(null);
        useMap(mapRef)
    
        return <div ref={mapRef} className='map' />
    }
    

     

    🟠 맵에서 '로드' 이벤트를 수신합니다.

    Well, so far we already have the reference to the map instance available.

    Now what we want to do is that when we load the map, a marker is displayed on the screen.

    For this, the Map instance has the method 'on' that allows us to listen to certain events that are triggered in the map.

    So, first we create a useEffect.

    useEffect(() => {
    
    }, [])
    

    Then, we are going to make an evaluation where if the mapInitRef.current exists (that is to say that it has the value of the instance),
    we execute the following event 'on()'.

    useEffect(() => {
    
        mapInitRef.current && mapInitRef.current.on();
    
    }, [])
    

    The on method in this case receives 2 parameters:

    • type: the action to listen to, in this case it will be the load action, since we want something to be executed when the map has already been loaded.
    • listener: the function to execute when the action is listened.
    useEffect(() => {
    
        mapInitRef.current && mapInitRef.current.on(
            'load', 
            () => {}
        )
    
    }, [])
    

    🔴 마커를 추가하는 기능 만들기.

    Now let's create a function to add markers to the map.

    Inside the folder src/utils we create the file generateNewMarker.ts and add a new function.

    This function receives as parameter:

    • lat: latitude.
    • lng: longitude.
    • map: the map to add the marker to.
    import { Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
    }
    

    To create a marker we create a new instance of the Marker class, which we send certain optional parameters:

    • color: color of the marker.
    • scale: size of the marker.
    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        new Marker({ color: '#63df29', scale: 1.5 })
    }
    

    Then, we execute the setLngLat method to send it the longitude and latitude as an array to tell the marker where it should be placed.

    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        new Marker({ color: '#63df29', scale: 1.5 })
            .setLngLat([lng, lat])
    }
    

    And finally we call the addTo method to add it to the map, we pass it the instance of the map that we received by parameter.

    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        new Marker({ color: '#63df29', scale: 1.5 })
            .setLngLat([lng, lat])
            .addTo(map)
    }
    

    An extra, would be to create a PopUp. For it we make a new instance of the class Popup (we save it in a constant), which we send certain parameters that are optional:

    • closeButton: show the close button, we set it to false.

    • anchor: the position where the PopUp should be shown in the marker.

    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        const popUp = new Popup({ closeButton: false, anchor: 'left', })
    
        new Marker({ color: '#63df29', scale: 1.5 })
            .setLngLat([lng, lat])
            .addTo(map)
    }
    

    And to place custom content to the PopUp, we will call the setHTML method and send it HTML as a string.

    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        const popUp = new Popup({ closeButton: false, anchor: 'left', })
            .setHTML(`<div class="popup">You click here: <br/>[${lng},  ${lat}]</div>`)
    
        new Marker({ color: '#63df29', scale: 1.5 })
            .setLngLat([lng, lat])
            .addTo(map)
    }
    

    Finally, to the instance of the Marker, before the addTo method, we place the setPopup method and send it the popUp constant.

    import { Popup, Marker, Map } from 'mapbox-gl';
    
    export const generateNewMarker = ({ lat, lng, map }: { lng: number, lat: number, map: Map }) => {
    
        const popUp = new Popup({ closeButton: false, anchor: 'left', })
            .setHTML(`<div class="popup">You click here: <br/>[${lng},  ${lat}]</div>`)
    
        new Marker({ color: '#63df29', scale: 1.5 })
            .setLngLat([lng, lat])
            .setPopup(popUp)
            .addTo(map)
    }
    

    It's time to call this method! 😉

     

    🟠 마커를 표시합니다.

    In our hook useMap, inside the useEffect where we were creating adding the event to listen to the map when it loads for the first time, we call the generateNewMarker method.

    useEffect(() => {
        mapInitRef.current && mapInitRef.current.on(
            'load', 
            () => generateNewMarker()
    }, [])
    

    To this method we send an object containing:

    • map: we send mapInitRef.current since it is the instance of the map.
    • the second parameter we send mapInitRef.current!.getCenter(). This function returns an array of two numbers that are the longitude and latitude (these numbers are those that we passed at the beginning, at the time of initializing the map), for which we spread them with the spread operator.
    useEffect(() => {
        mapInitRef.current && mapInitRef.current.on(
            'load', 
            () => generateNewMarker({ 
                map: mapInitRef.current!, 
                ...mapInitRef.current!.getCenter() 
            })
    }, [])
    

    Finally, it is good practice that when we are listening to events within a useEffect, when the component is disassembled (which in this case will not happen because we only have one view which is the map), it is necessary to stop listening to the event and not execute anything.

    useEffect(() => {
        mapInitRef.current && mapInitRef.current.on(
            'load', 
            () => generateNewMarker({ 
                map: mapInitRef.current!, 
                ...mapInitRef.current!.getCenter() 
            })
    
        return () => { 
            mapInitRef.current?.off('load', generateNewMarker) 
        }
    }, [])
    

     

    This is what the marker would look like on our map. 🥳

    10451097 0611096

    🧵 더블 클릭 시 지도에 새 마커 추가.

    This will be very simple, since we have almost everything done.
    It is only necessary, to add a new effect in our custom hook.

    And following the same practices as when we listened to the 'load' event before.

    • We validate that mapInitRef contains the map instance.

    • We call the on method to listen for the 'dblclick' event.

    • Now, the listener that is executed gives us access to the longitude and latitude (which come as an array of two numbers), which we can unstructure from the listener.

    • We execute the function generateNewMarker.

    • To the function generateNewMarker we send the map, which will have the value of the map instance found in mapInitRef.current. Then, we spread the value of lngLat given to us by the listener.

    • We clean the effect with the return, stopping listening to the 'dblclick' event.

    useEffect(() => {
    
        mapInitRef.current && mapInitRef.current.on(
            'dblclick', 
            ({ lngLat }) => generateNewMarker({ 
                map: mapInitRef.current!, 
                ...lngLat 
            }))
    
        return () => { 
            mapInitRef.current?.off('dblclick', generateNewMarker) 
        }
    
    }, [])
    

     

    This is how the markers would look on our map. 🥳



    🧵결론.

    방금 보여드린 전체 프로세스는 React JS로 지도를 표시하는 방법 중 하나입니다. 🗺️ 이 연습을 수행하는 방법을 이해하는 데 도움이 되었기를 바랍니다. 여기까지 해주셔서 대단히 감사합니다! 🤗 React JS로 지도를 표시하는 다른 방법이나 더 나은 방법을 알고 있다면 의견을 말해주세요. 🙌 콘텐츠가 마음에 든다면 이 게시물에 반응하거나 이 게시물을 관심 있는 사람과 공유하여 저를 지원하는 것을 잊지 마세요! ❤️ 🧵 소스 코드.

    /


    MapBox 라이브러리에서 지도를 표시하고 이벤트를 실행하여 지도에 마커를 추가하는 애플리케이션입니다. 🗺️





    React로 MapBox 맵을 표시합니다. 🗺️


    MapBox 라이브러리에서 지도를 표시하고 이벤트를 실행하여 지도에 마커를 추가하는 애플리케이션입니다. 🗺️

    Franklin361

    특징 ⚙️


  • 전체 화면 지도를 봅니다.
  • 지도를 로드할 때 초기 위치에 마커를 배치하십시오.
  • 지도를 두 번 클릭하면 새 마커를 추가합니다.

  • 기술 🧪


  • 반응 JS
  • 타입스크립트
  • Vite JS
  • show-map

  • 설치 🧰


  • 저장소를 복제합니다(이 설치되어 있어야 함).
  •     git clone https://github.com/Franklin361/show-map
  • 프로젝트의 종속성을 설치합니다.
  •     npm install
  • 프로젝트를 실행하십시오.
  •     npm run dev

    참고: 테스트를 실행하려면 다음 명령을 사용하십시오.
        npm run test

    링크⛓️


    응용 프로그램 데모 🔥



  • 🔗 MapBox

  • 보고 싶은 경우를 대비하여 튜토리얼에 대한 링크가 있습니다! 눈👀



  • 🇲🇽 🔗

  • 🇺🇲 🔗


  • Git

    좋은 웹페이지 즐겨찾기