MapBoxの地図をReactで表示する 🗺️
この投稿の目的は、React JSアプリケーションでMapBox GL JSライブラリーを利用してインタラクティブな地図を表示する方法を教えることです。
今回は地図を表示し、ダブルクリックした場所にマーカーを置くイベントを追加する方法を紹介します。
🚨 注意: この投稿には、**ReactとTypeScript(基本的なフックとfetchによるリクエスト)**の基本を理解していることが必要です。
どんなフィードバックや改善も歓迎します。ありがとうございます、記事を楽しんでください。🤗
目次
📍 マーカーの表示
使用する技術
- ▶️ React JS(バージョン18)
- ▶️ Vite JS
- ▶️ TypeScript
- ▶️ MapBox
- ▶️ CSSバニラ(この投稿の最後のリポジトリでスタイルを確認できます)
コーディングを始める前に...
MapBoxの地図を使い始める前にいくつかの事前準備があります。
1- MapBoxにアカウントを作成する必要があります。
2- アカウントでデフォルトで作成されたアクセストークンを探すか、新しいアクセストークンを作成します。
3- 後で使うためにこのアクセストークンを保存しておきます。
プロジェクトの作成
プロジェクトには「show-map
」という名前をつけます(好きな名前をつけてもいいですが)。
npm init vite@latest
/終了する
Vite JSでプロジェクトを作り、React with TypeScriptを選択します。
次に、以下のコマンドを実行して新しく作成されたディレクトリに移動します。
cd show-map
/終了する
次に、依存関係をインストールします。
npm install
/終了する
その後、コードエディターでプロジェクトを開きます(私の場合はVS Code)。
code .
/終了する
最初の一歩
アプリケーションでMapBoxを使うためにインストールする必要があります。
npm i mapbox-gl
/終了する
TypeScriptを使っているので、MapBoxの型定義もインストールする必要があります。
npm i -D @types/mapbox-gl
/終了する
フォルダ**src/App.tsx
の中身を全て削除し、とりあえず "Hello world" というh1**を置いておきます。
const App = () => {
return (
<div>
<h1>Hello World</h1>
</div>
)
}
export default App
/終了する
🚨 注意: MapBoxのスタイルを使えるようにするため、src/main.tsxファイルの一番上でインポートしておくといいでしょう。
行を追加するには:
import 'mapbox-gl/dist/mapbox-gl.css'
/終了する
こうすると、src/main.tsxファイルがこのようになります。
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>
)
/終了する
地図を表示するためのコンポーネントを作る
src/componentsフォルダを作成し、MapView.tsxファイルを作成します。
地図を表示するにはdivタグだけが必要です。
🚨 注意: 地図を表示する前に、このコンポーネントにスタイル、高さ、幅を設定して、後で地図を正しく見ることができるようにする必要があります。
export const MapView = () => {
return (
<div className='map' />
)
}
/終了する
画面に地図を表示する
地図を表示するには2つのフックが必要です。
最初に使うのはuseRefです。useRefを使って、地図がレンダリングされるdivの参照を保存します。
次に使うフックはuseEffectです。このフックを使って地図を初期化します。
🟠 マップコンテナへの参照の保持
次のようにuseRefフックを使ってこのタスクを行います:
import { useRef } from 'react';
export const MapView = () => {
const mapRef = useRef<HTMLDivElement>(null);
return <div ref={mapRef} className='map' />
}
/終了する
🔴 なぜ参照を保持する必要があるのでしょうか?
さて、divにIDを付けてそれで機能するだけだとしてもいいですよ。😌
問題は、複数のマップを使いたい場合です。🤔
MapViewコンポーネントを複数使うと、同じIDを持つため、1つのマップだけがレンダリングされます。この問題を避けるために、useRefフックを使用します。MapViewコンポーネントを再利用するたびに、新しい参照が作成されます。
🟠 MapBoxの初期化
src/utilsフォルダを作成し、新しいファイルinitMap.tsを作成して、そこで地図を初期化するための関数を作ります。
この関数は以下を受け取る必要があります:
- container: HTML要素、この場合は地図がレンダリングされるdivです。
- coords: 場所の座標です。2つの数字からなる配列で、最初の位置が経度、次の位置が緯度である必要があります。
import { Map } from 'mapbox-gl';
export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
}
/終了する
関数の中で、新しいMapのインスタンスを返します。
後でもっとイベントやアクションをしたい場合は、そのインスタンスが必要になるため、返します。ただ地図を表示して終わりの場合は、何も返さなくてもいいです。
import { Map } from 'mapbox-gl';
export const initMap = (container: HTMLDivElement, coords: [number, number]) => {
return new Map();
}
/終了する
Mapクラスはいくつかのオプションを必要とします。
-
container: 地図がレンダリングされるHTML要素で、関数のパラメータで渡されたcontainerに設定します。
-
style: 地図のスタイルで、この場合はダークスタイルを使用します。MapBoxのドキュメントには他のスタイルもあります。
-
pitchWithRotate: 地図の傾斜操作で、この場合は無効にするためにfalseに設定します。
-
center: 初期化時に地図が位置する座標で、関数のパラメータで渡されたcoordsに設定します。
-
zoom: 地図の初期ズームレベルで、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を使って作成した関数を呼び出します。www.notion.so/tools/dictionaries
useEffect内で、useRefの値があった場合にのみ地図を初期化します。
initMapには、mapRefの**currentプロパティにある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' />
)
}
/終了する
これで画面上に地図が表示されます。🥳 以下に示す画像のようになります。
さて、それからどうしましょうか?
もしマーカーを追加するイベントについて話し合いましょうか。😉
初期位置にマーカーを追加する
地図でイベントを作成する前に、Mapのインスタンスへの参照を保持する必要があります。それにはまたuseRefを使います。www.notion.so/tools/dictionaries
mapInitRefという新しい参照を作成し、それがmap型またはnullになるようにします。
initMap関数はMapのインスタンスを返すため、mapInitRefにこのインスタンスを割り当てます。
const mapInitRef = useRef<Map | null>(null);
useEffect(() => {
if (mapRef.current) {
mapInitRef.current = initMap(
mapRef.current,
[-100.31019063199852, 25.66901932031443]
);
}
}, []);
/終了する
コンポーネントが大きくなる前に
🟠 コンポーネントが大きくなる前に...
この時点で、MapViewコンポーネントのロジックをクリーンに保つために、地図のロジックを扱うカスタムフックを作成し、コードをリファクタリングする方が良いでしょう。
src/hooksフォルダを作成し、useMap.tsファイルをその中に作り、useMap.tsファイルにMapViewのロジックを移動します。www.notion.so/tools/dictionaries
このカスタムフックは、地図がレンダリングされるコンテナをパラメータとして受け取ります。
これで、mapRefを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]
);
}
}, []);
}
/終了する
そして、MapViewコンポーネントでそのフックを呼び出します。
そして、これが結果のコンポーネントになります。ずっと読みやすくなりますよね。😉
import { useRef } from 'react';;
import { useMap } from '../hook/useMap';
export const MapView = () => {
const mapRef = useRef<HTMLDivElement>(null);
useMap(mapRef)
return <div ref={map<br><br>こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。<br>[https://dev.to/franklin030601/mostrando-mapa-de-mapbox-con-react-82k](https://dev.to/franklin030601/mostrando-mapa-de-mapbox-con-react-82k)