Last active
February 10, 2019 23:41
-
-
Save terrierscript/861df9328f80f077ac0b534569f3e8e1 to your computer and use it in GitHub Desktop.
react-hooks-google-map-part4-A
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 useDrawMapMarkers = ({ | |
markers, | |
googleMap, | |
map, | |
onClickMarker | |
}) => { | |
const markerObjectsRef = useRef({}); | |
useEffect( | |
() => { | |
const { Marker } = googleMap.maps; | |
markers.map(({ id, position }) => { | |
// すでに描画済みなmarkerだったら描画しない | |
if (markerObjectsRef.current[id]) { | |
return; | |
} | |
const markerObj = new Marker({ | |
position, | |
map, | |
title: "marker!" | |
}); | |
// markerがクリックされた時のイベントを追加する | |
markerObj.addListener("click", e => { | |
onClickMarker(id, markerObj, markerObjectsRef.current, e); | |
}); | |
markerObjectsRef.current[id] = markerObj; | |
}); | |
}, | |
[markers, 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( | |
() => { | |
return 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 => { | |
console.log(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, | |
useDrawMapMarkers, | |
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%; | |
`; | |
const useMapMarkerSetup = ({ googleMap, map }) => { | |
// stateとして管理するマーカー | |
const { addMarker, removeMarker, getMarkers } = useMarkerState( | |
initialMarkers | |
); | |
const markers = getMarkers(); | |
// 描画する | |
useDrawMapMarkers({ | |
markers, | |
googleMap, | |
map, | |
// 削除イベントを追加 | |
onClickMarker: (id, markerObj, markerObjectsRef) => { | |
removeMarker(id); | |
markerObj.setMap(null); | |
markerObjectsRef[id] = null; | |
} | |
}); | |
// クリックイベントを追加 | |
useMapClickEvent({ | |
onClickMap: ({ lat, lng }) => { | |
addMarker({ lat, lng }); | |
}, | |
map, | |
googleMap | |
}); | |
}; | |
const MapMarkers = ({ googleMap, map }) => { | |
useMapMarkerSetup({ googleMap, map }); | |
return null; | |
}; | |
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