概要
このチュートリアルでは、@googlemaps/react-wrapper を使って React アプリケーションに Map と Marker を追加して、地図とマーカーをアプリケーション状態に組み込む方法について説明します。
@googlemaps/react-wrapper をインストールする
@googlemaps/react-wrapper ライブラリをインストールし、コンポーネントのレンダリング時にそのライブラリを使って、Maps JavaScript API を動的に読み込みます。
npm install @googlemaps/react-wrapper
このライブラリは、次のようにインポートして、使用できます。
import { Wrapper, Status } from "@googlemaps/react-wrapper";
このコンポーネントの基本的な使用方法は、Maps JavaScript API に依存する子コンポーネントをラップすることです。Wrapper
コンポーネントは、読み込みコンポーネントのレンダリング、または Maps JavaScript API の読み込みエラーの処理を行う render
プロパティも受け入れます。
const render = (status: Status) => {
return <h1>{status}</h1>;
};
<Wrapper apiKey={"YOUR_API_KEY"} render={render}>
<YourComponent/>
</Wrapper>
地図コンポーネントを追加する
地図をレンダリングする基本的な機能コンポーネントでは、ほとんどの場合 useRef
、useState
、useEffect
の React のフックが使用されます。
最初の地図コンポーネントには次のような署名があります。
const Map: React.FC<{}> = () => {};
google.maps.Map
にはコンストラクタ パラメータとして Element
が必要なため、コンポーネントの存続期間中は持続する可変オブジェクトを維持するために useRef が必要です。次のスニペットは、Map
コンポーネントの本文の useEffect フック内で地図をインスタンス化します。
TypeScript
const ref = React.useRef<HTMLDivElement>(null); const [map, setMap] = React.useState<google.maps.Map>(); React.useEffect(() => { if (ref.current && !map) { setMap(new window.google.maps.Map(ref.current, {})); } }, [ref, map]);
JavaScript
const ref = React.useRef(null); const [map, setMap] = React.useState(); React.useEffect(() => { if (ref.current && !map) { setMap(new window.google.maps.Map(ref.current, {})); } }, [ref, map]);
上記の useEffect
フックは、ref
が変更された場合にのみ実行されるようになり、現在は Map
コンポーネントが次を返ようになりました。
return <div ref={ref} />
props を追加して地図コンポーネントを拡張する
地図のオプション、イベント リスナー、地図を含む div に適用されるスタイルの props を追加すると、基本的な地図コンポーネントを拡張できます。次のコードは、この機能コンポーネントの展開されたインターフェースを示しています。
interface MapProps extends google.maps.MapOptions {
style: { [key: string]: string };
onClick?: (e: google.maps.MapMouseEvent) => void;
onIdle?: (map: google.maps.Map) => void;
}
const Map: React.FC<MapProps> = ({
onClick,
onIdle,
children,
style,
...options
}) => {}
style
オブジェクトはそのまま渡され、レンダリングされる div
でプロパティとして設定されます。
return <div ref={ref} style={style} />;
onClick
、onIdle
、google.maps.MapOptions
では、google.maps.Map
に更新を強制的に適用するのに useEffect
フックが必要です。
TypeScript
// because React does not do deep comparisons, a custom hook is used // see discussion in https://github.com/googlemaps/js-samples/issues/946 useDeepCompareEffectForMaps(() => { if (map) { map.setOptions(options); } }, [map, options]);
JavaScript
// because React does not do deep comparisons, a custom hook is used // see discussion in https://github.com/googlemaps/js-samples/issues/946 useDeepCompareEffectForMaps(() => { if (map) { map.setOptions(options); } }, [map, options]);
イベント リスナーでは、プロパティとして渡されたハンドラが更新された場合、既存のリスナーを削除するために、やや複雑なコードが必要になります。
TypeScript
React.useEffect(() => { if (map) { ["click", "idle"].forEach((eventName) => google.maps.event.clearListeners(map, eventName) ); if (onClick) { map.addListener("click", onClick); } if (onIdle) { map.addListener("idle", () => onIdle(map)); } } }, [map, onClick, onIdle]);
JavaScript
React.useEffect(() => { if (map) { ["click", "idle"].forEach((eventName) => google.maps.event.clearListeners(map, eventName) ); if (onClick) { map.addListener("click", onClick); } if (onIdle) { map.addListener("idle", () => onIdle(map)); } } }, [map, onClick, onIdle]);
マーカー コンポーネントを作成する
マーカー コンポーネントは、useEffect
フックと useState
フックを含む地図コンポーネントと同様のパターンを使用します。
TypeScript
const Marker: React.FC<google.maps.MarkerOptions> = (options) => { const [marker, setMarker] = React.useState<google.maps.Marker>(); React.useEffect(() => { if (!marker) { setMarker(new google.maps.Marker()); } // remove marker from map on unmount return () => { if (marker) { marker.setMap(null); } }; }, [marker]); React.useEffect(() => { if (marker) { marker.setOptions(options); } }, [marker, options]); return null; };
JavaScript
const Marker = (options) => { const [marker, setMarker] = React.useState(); React.useEffect(() => { if (!marker) { setMarker(new google.maps.Marker()); } // remove marker from map on unmount return () => { if (marker) { marker.setMap(null); } }; }, [marker]); React.useEffect(() => { if (marker) { marker.setOptions(options); } }, [marker, options]); return null; };
google.maps.Map
が DOM 操作を管理するため、コンポーネントは null を返します。
地図の子コンポーネントとしてマーカーを追加する
地図にマーカーを追加するために、次のように、特別な children
プロパティを使用して Marker
コンポーネントが Map
コンポーネントに渡されます。
<Wrapper apiKey={"YOUR_API_KEY"}>
<Map center={center} zoom={zoom}>
<Marker position={position} />
</Map>
</Wrapper>
すべての子に追加のプロパティとして google.maps.Map
オブジェクトを渡すには、Map
コンポーネントの出力に若干の変更を加える必要があります。
TypeScript
return ( <> <div ref={ref} style={style} /> {React.Children.map(children, (child) => { if (React.isValidElement(child)) { // set the map prop on the child component // @ts-ignore return React.cloneElement(child, { map }); } })} </> );
JavaScript
return ( <> <div ref={ref} style={style} /> {React.Children.map(children, (child) => { if (React.isValidElement(child)) { // set the map prop on the child component // @ts-ignore return React.cloneElement(child, { map }); } })} </> );
地図とアプリケーション状態をリンクする
onClick
コールバックと onIdle
コールバックに上記のパターンを使用すると、アプリケーションを拡張して、地図のクリックやパンなどのユーザー アクションを完全に統合できます。
TypeScript
const [clicks, setClicks] = React.useState<google.maps.LatLng[]>([]); const [zoom, setZoom] = React.useState(3); // initial zoom const [center, setCenter] = React.useState<google.maps.LatLngLiteral>({ lat: 0, lng: 0, }); const onClick = (e: google.maps.MapMouseEvent) => { // avoid directly mutating state setClicks([...clicks, e.latLng!]); }; const onIdle = (m: google.maps.Map) => { console.log("onIdle"); setZoom(m.getZoom()!); setCenter(m.getCenter()!.toJSON()); };
JavaScript
const [clicks, setClicks] = React.useState([]); const [zoom, setZoom] = React.useState(3); // initial zoom const [center, setCenter] = React.useState({ lat: 0, lng: 0, }); const onClick = (e) => { // avoid directly mutating state setClicks([...clicks, e.latLng]); }; const onIdle = (m) => { console.log("onIdle"); setZoom(m.getZoom()); setCenter(m.getCenter().toJSON()); };
これらのフックは、緯度入力で示したように次のパターンを使用してフォーム要素に統合できます。
<label htmlFor="lat">Latitude</label>
<input
type="number"
id="lat"
name="lat"
value={center.lat}
onChange={(event) =>
setCenter({ ...center, lat: Number(event.target.value) })
}
/>
これで、アプリケーションはクリックをトラッキングし、各クリックの位置にマーカーをレンダリングできます。
{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}
コードを確認する
完全なサンプルコードについては、以下のオンライン コード プレイグラウンドを確認するか Git リポジトリのクローンを作成してください。
サンプルを試す
サンプルのクローンを作成する
このサンプルをローカルで実行するには、Git と Node.js が必要です。こちらの手順に沿って Node.js と NPM をインストールします。次のコマンドで、クローンを作成し、依存関係をインストールしてサンプル アプリケーションを起動します。
git clone -b sample-react-map https://github.com/googlemaps/js-samples.git
cd js-samples
npm i
npm start
他のサンプルを試すには、sample-SAMPLE_NAME
で始まるブランチに切り替えます。
git checkout sample-SAMPLE_NAME
npm i
npm start