Created
February 10, 2019 23:36
-
-
Save terrierscript/f57ace64b776848d68be6b5bc736e2ed to your computer and use it in GitHub Desktop.
react-hooks-google-map-4-B
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useState, useCallback, useReducer, useRef } from "react"; | |
import GoogleMapsApiLoader from "google-maps-api-loader"; | |
// @ts-ignore | |
import uuid from "uuid/v4"; | |
// Google Mapのオブジェクトを呼び出すだけのhooks | |
export const useGoogleMap = apiKey => { | |
const [googleMap, setGoogleMap] = useState(null); | |
useEffect(() => { | |
GoogleMapsApiLoader({ apiKey }).then(google => { | |
setGoogleMap(google); | |
}); | |
}, []); // useEffectの第二引数を[]にすることで、初回1回目だけ実行される | |
return googleMap; | |
}; | |
// 実際にMapをDOMにマウントする処理。 | |
export const useMap = ({ googleMap, mapContainerRef, initialConfig }) => { | |
const [map, setMap] = useState(null); | |
useEffect( | |
() => { | |
// googleMapかmapContainerRefが初期化されてなければ何もしない | |
if (!googleMap || !mapContainerRef.current) { | |
return; | |
} | |
const map = new googleMap.maps.Map( | |
mapContainerRef.current, | |
initialConfig | |
); | |
setMap(map); | |
}, | |
[googleMap, mapContainerRef] | |
); // initialConfigは変わったとしても再マウントするとおかしなことになるので更新対象にしない // googleMapかmapContainerRefが変化したらeffectが発火する。 | |
// あとで使えるようにmapを返すようにする | |
return map; | |
}; | |
// 単体でマーカーを描画する | |
export const useDrawMapMarker = ({ | |
position, | |
googleMap, | |
map, | |
onClickMarker | |
}) => { | |
const markerObjectsRef = useRef(null); | |
useEffect( | |
() => { | |
const { Marker } = googleMap.maps; | |
// すでに描画済みなmarkerだったら描画しない | |
if (markerObjectsRef.current) { | |
return; | |
} | |
const markerObj = new Marker({ | |
position, | |
map, | |
title: "marker!" | |
}); | |
// markerがクリックされた時のイベントを追加する | |
markerObj.addListener("click", e => { | |
onClickMarker(e); | |
}); | |
markerObjectsRef.current = markerObj; | |
// コンポーネントが消えたらmarkerもmapから消すように仕掛ける。これはすっ | |
return () => { | |
if (markerObjectsRef.current === null) { | |
return; | |
} | |
markerObjectsRef.current.setMap(null); | |
}; | |
}, | |
[googleMap, map] | |
); | |
}; | |
const markerReducer = (state, action) => { | |
switch (action.type) { | |
case "ADD": | |
const id = uuid(); | |
return { | |
...state, | |
[id]: action.payload | |
}; | |
case "REMOVE": | |
const { [action.payload]: removeItem, ...rest } = state; | |
return rest; | |
default: | |
return state; | |
} | |
}; | |
const mapReducerInitializer = initialMarkers => { | |
return initialMarkers.reduce((state, marker) => { | |
return markerReducer(state, { | |
type: "ADD", | |
payload: marker | |
}); | |
}, {}); | |
}; | |
// markerをstate管理する | |
export const useMarkerState = initialMarkers => { | |
const [markers, dispatch] = useReducer( | |
markerReducer, | |
initialMarkers, | |
mapReducerInitializer | |
); | |
// マーカーの追加・削除のaction関数 | |
const addMarker = useCallback( | |
position => dispatch({ type: "ADD", payload: position }), | |
[dispatch] | |
); | |
const removeMarker = useCallback( | |
removeUuid => dispatch({ type: "REMOVE", payload: removeUuid }), | |
[dispatch] | |
); | |
// 外向けにはobjectではなくarrayとして返す | |
const getMarkers = useCallback( | |
() => Object.entries(markers).map(([id, position]) => ({ id, position })), | |
[markers] | |
); | |
return { | |
// markers // objectとしてのmarkerは隠蔽する | |
addMarker, | |
removeMarker, | |
getMarkers | |
}; | |
}; | |
// Mapがクリックされたらイベントを飛ばすhooks | |
export const useMapClickEvent = ({ onClickMap, googleMap, map }) => { | |
useEffect( | |
() => { | |
const listener = googleMap.maps.event.addListener(map, "click", e => { | |
onClickMap({ | |
lat: e.latLng.lat(), | |
lng: e.latLng.lng() | |
}); | |
}); | |
// onClickMapが変更されたらつくったイベントをクリアする | |
//(じゃないとクリックするたびにイベントが大量閣下さえる) | |
return () => { | |
googleMap.maps.event.removeListener(listener); | |
}; | |
}, | |
[googleMap, map, onClickMap] | |
); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef } from "react"; | |
import ReactDOM from "react-dom"; | |
import { | |
useGoogleMap, | |
useMap, | |
useDrawMapMarker, | |
useMarkerState, | |
useMapClickEvent | |
} from "./hooks"; | |
import styled from "styled-components"; | |
const API_KEY = undefined; | |
const initialConfig = { | |
zoom: 12, | |
center: { lat: 35.6432027, lng: 139.6729435 } | |
}; | |
const initialMarkers = [ | |
{ lat: 35.6432027, lng: 139.6729435 }, | |
{ lat: 35.5279833, lng: 139.6989209 }, | |
{ lat: 35.6563623, lng: 139.7215211 }, | |
{ lat: 35.6167531, lng: 139.5469376 }, | |
{ lat: 35.6950961, lng: 139.5037899 } | |
]; | |
const MapContainer = styled.div` | |
height: 100vh; | |
width: 100%; | |
`; | |
// marker一つ一つを担当するComponent | |
const Marker = ({ googleMap, map, position, onClickMarker }) => { | |
useDrawMapMarker({ | |
googleMap, | |
map, | |
position, | |
onClickMarker | |
}); | |
return null; | |
}; | |
const useMapMarkerSetup = ({ googleMap, map }) => { | |
const { addMarker, removeMarker, getMarkers } = useMarkerState( | |
initialMarkers | |
); | |
const markers = getMarkers(); | |
// クリックイベントを追加 | |
useMapClickEvent({ | |
onClickMap: ({ lat, lng }) => { | |
addMarker({ lat, lng }); | |
}, | |
map, | |
googleMap | |
}); | |
return { markers, removeMarker }; | |
}; | |
const MapMarkers = ({ map, googleMap }) => { | |
const { markers, removeMarker } = useMapMarkerSetup({ map, googleMap }); | |
return ( | |
<> | |
{markers.map(({ id, position }) => ( | |
<Marker | |
key={id} // hooksがkeyに紐づく。これがないと適切なマーカーが消えなくなる | |
position={position} | |
onClickMarker={() => { | |
removeMarker(id); | |
}} | |
map={map} | |
googleMap={googleMap} | |
/> | |
))} | |
</> | |
); | |
}; | |
const WaitForMap = ({ googleMap, map, children }) => { | |
if (!googleMap || !map) { | |
return null; | |
} | |
return children; | |
}; | |
export const MapApp = () => { | |
const googleMap = useGoogleMap(API_KEY); | |
const mapContainerRef = useRef(null); | |
const map = useMap({ | |
googleMap, | |
mapContainerRef, | |
initialConfig | |
}); | |
return ( | |
<> | |
<MapContainer ref={mapContainerRef} /> | |
<WaitForMap googleMap={googleMap} map={map}> | |
<MapMarkers googleMap={googleMap} map={map} /> | |
</WaitForMap> | |
</> | |
); | |
}; | |
const rootElement = document.getElementById("root"); | |
ReactDOM.render(<MapApp />, rootElement); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment