概要
google mapでマーカーのInfoWindowの中身の記述にReactを使う方法を二つ紹介します
- react-google-mapsを使う方法(個人的にはこちらがおすすめかと思います)
- InfoWindowのdomに新しいreactをrenderする方法
思い立ったきっかけ
WEBサイトを作るとき位置表示したい時ってgoogle mapを使うことってよくあります。さらに位置表示場所に説明をつけたい時とかはマーカーにInfoWindow使ってポップアップをつけるってのは割とよくある手法じゃないかと思います
ただ、このポップアップの内容はデフォルトだと文字列のhtmlを書いて記述するようにと説明されています。ここ参照
var contentString =
'<div id="content">' +
'<div id="siteNotice">' +
'</div>' +
'<h1 id="firstHeading" class="firstHeading">Uluru</h1>' +
'<div id="bodyContent">' +
'<p><b>Uluru</b>, also referred to as <b>Ayers Rock</b>, is a large ' +
'sandstone rock formation in the southern part of the ' +
'Northern Territory, central Australia. It lies 335 km (208 mi) ' +
'south west of the nearest large town, Alice Springs; 450 km ' +
'(280 mi) by road. Kata Tjuta and Uluru are the two major ' +
'features of the Uluru - Kata Tjuta National Park. Uluru is ' +
'sacred to the Pitjantjatjara and Yankunytjatjara, the ' +
'Aboriginal people of the area. It has many springs, waterholes, ' +
'rock caves and ancient paintings. Uluru is listed as a World ' +
'Heritage Site.</p>' +
'<p>Attribution: Uluru, <a href="https://en.wikipedia.org/w/index.php?title=Uluru&oldid=297882194">' +
'https://en.wikipedia.org/w/index.php?title=Uluru</a> ' +
'(last visited June 22, 2009).</p>' +
'</div>' +
'</div>';
これ結構面倒だなって言うのと、特に自分はReactでMATERIAL-UI使ってフロント書くことが多いので他のデザインと差異が出ちゃうことが結構気に食わなかったと言うことがありました
react-google-mapsを使う方法
まずはreact-google-mapsの紹介です。こちらはそもそもgoogle mapを記述するときにReactの形式で書けると言うものになります。
例えば通常はgoogle mapを使うときには以下のようにhtmlでgoogle mapを読み込み、domに対してgoogle mapを紐付ける記述をjsでする必要がありました
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap" async defer></script>
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: uluru
});
対してreact-google-mapsでは以下のようにReactの中でgoogle mapの読み込みやズーム、初期位置を設定することができます。また、Google Maps JavaScript API v3でできることはだいたいできるそうです。
GoogleMapタグの中にMarkerタグを記述するとマーカーを表示することができ、並列に書けばマーカーを増やしていくこともできます。また、別のコンポーネントにマーカーをまとめて記述して子コンポーネントとして入れることも可能です。
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
const MapWithAMarker = withScriptjs(withGoogleMap(props =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: -34.397, lng: 150.644 }}
>
<Marker
position={{ lat: -34.397, lng: 150.644 }}
/>
</GoogleMap>
));
<MapWithAMarker
googleMapURL="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
react-google-mapsでInfoWindowを設定する方法はマーカーのタグの中にさらにInfoWindowタグを入れることで実現できます。
const { compose, withProps, withStateHandlers } = require("recompose");
const FaAnchor = require("react-icons/lib/fa/anchor");
const {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow,
} = require("react-google-maps");
const MapWithAMakredInfoWindow = compose(
withStateHandlers(() => ({
isOpen: false,
}), {
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen,
})
}),
withScriptjs,
withGoogleMap
)(props =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: -34.397, lng: 150.644 }}
>
<Marker
position={{ lat: -34.397, lng: 150.644 }}
onClick={props.onToggleOpen}
>
{props.isOpen && <InfoWindow onCloseClick={props.onToggleOpen}>
<FaAnchor />
</InfoWindow>}
</Marker>
</GoogleMap>
);
<MapWithAMakredInfoWindow
googleMapURL="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
InfoWindowの子コンポーネントがInfoWindowのメッセージとなって表示されます。個人的な感覚ではダイアログと同じような感覚で書けば大丈夫と思います。
Reactの他のgoogle mapライブラリ
google mapをreactでラップしたライブラリは他にもあり、google-map-react、google-maps-reactなどが自分が調べた限りではありました。
自分はこの三つのうち一番ドキュメントがわかりやすくgithubのスターが多かったreact-google-mapsを使っていましたが、好みなどもあるかもしれませんので合わなかったら他のものを試してみても良いと思います。
InfoWindowのdomに新しいreactをrenderする方法
二つ目はここの記事を参考にしていますがInfoWindowにidをふったdivタグを作成し、そのidに対してrenderを行うと言う方法です。
この方法で気をつけるべき所はdomreadyでdomが生成された後にrenderしないとうまく行かないと言う所です。生成されてない状態でrenderしても何も表示されずに自分は一度ここでつまづきました。
createInfoWindow(e, map) {
const infoWindow = new window.google.maps.InfoWindow({
content: '<div id="infoWindow" />',
position: { lat: e.latLng.lat(), lng: e.latLng.lng() }
})
infoWindow.addListener('domready', e => {
render(<InfoWindow />, document.getElementById('infoWindow'))
})
infoWindow.open(map)
}
ちなみにこの方法で<InfoWindow />
にpropsとしてコールバック関数などを渡すこともできるのでInfoWindowの中でボタンを設置してアクションを起こしたいといったことも可能です。
また、reduxなどでデータ管理を行なっている場合、生成したreactコンポーネントは別と認識されると思っていたのでreduxを更新したい場合も自分はコールバックでreduxのactionを呼び出すといった対応していました。
まとめ
個人的には初めからコードを書くといった場合にはreact-google-mapsで書いていくのがいいと思います。google mapをReactに組み込めてかけるので全体的にコード量が減りますし再利用もしやすくなる印象です。現在あるコードを使ってやりたいと言う場合はdomを生成する方法でもいいのではないかと思います。
余談
react-google-mapsには一点大きな欠点があって中心やズームをjsで動的に変更したい場合、ドキュメントでは<GoogleMap />
のpropsのcenterやzoomを更新するとあるのですがその中心座標などをreduxで管理してマーカーのactionによって変更していた場合、親コンポーネントが更新される→子コンポーネントが更新される→reduxが更新される→親コンポーネントが更新される・・・と言うループに陥ります。
これを回避する方法は自分が調べた限りだと一つだけありました。
まず、以下のように記述するとthis.mapにmapのオブジェクトが入ります。
<GoogleMap
defaultZoom={18}
defaultCenter={{ lat: 35, lng: 137 }}
ref={map => {
this.map = map;
}}
>
{this.props.children}
</GoogleMap>
このオブジェクトのthis.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
にnew google.maps.Map
で得られるオブジェクトが入っているようでthis.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.setCenter()
とすれば中心を変更することができます。
なのでこのthis.mapをマーカーのコンポーネントに渡しておきマーカーコンポーネントの中で中心座標の更新を行うことで先ほどの問題を解決できます。
ちょっとした小ネタですがこれもつまづいたので書いておこうと思います
参考
react-google-maps
Using Google Maps in React without custom libraries