【发布时间】:2016-11-03 14:07:42
【问题描述】:
我正在努力寻找在 react-redux 项目中呈现自定义谷歌地图标记(覆盖)的正确方法。我所拥有的是一个显示地图和搜索框的页面。当有人搜索某个地点并找到该地点时,这会触发 map idle 事件,然后我会更新地图边界和搜索到的地点信息并将它们保存在 redux 存储中。然后使用当前地图边界和城市名称获取数据。当数据到达时,过滤列表(过滤将在将来转到后端,这意味着服务器将发送已过滤的列表,这些列表落在当前视口中)并为地图上的每个列表呈现自定义叠加层。
关于地图空闲事件:
1) 更新地图边界和搜索到的地名
2) 从服务器获取一些数据(json 格式)
3) 过滤列表,这样我们就可以只渲染位置在当前视口(地图边界)内的列表
4) 为每个列表呈现自定义叠加层
对于每个地图空闲事件(在您缩放或平移地图后似乎发生),更新、获取、过滤和渲染的整个过程都会重复。
到目前为止,我所做的是项目一直在工作,直到 React 需要确定应该删除哪些覆盖层以及应该重新绘制哪些覆盖层。
实际上现在不能正常工作的是当可见列表数组更新时,React 只是删除了位于数组末尾的列表(从最后一个索引到 0)而不是正确的列表(位置为出视口)。
此外,有时,如果您已经搜索过一个地点,并在地图上玩了一会儿,然后尝试搜索另一个地点,则新的地点叠加层的位置不正确。相反,它们远离地图视口。
我对所有 Ract、Redux 和 Google Maps Api 技术都比较陌生,所以我知道我可能会做一些非常愚蠢的事情。我希望这里的人能够指出我正确的方向。我已经搜索了整个网络,但找不到正确的答案。我找到了一些关于如何创建自定义叠加层以及如何为谷歌地图创建反应组件的有用信息,而且我还知道有几个很好的 npm 模块可以完成我想要的工作(比如这个:react- google-maps 和这个:google-map-react),但它们都有自己的问题,而且对于我想要实现的目标来说太复杂了。
很抱歉在这里粘贴了所有代码,但我不确定如何在 jsbin 或类似代码 bin 中表示整个项目环境。如果我需要制作这样的代码工作示例,请告诉我,我会尝试。
这是我现在拥有的代码。当然,这只是对问题很重要的部分:
地图组件
import React, { PropTypes, Component } from 'react';
import SearchBar from '../SearchBar';
import OverlayViewComponent from '../OverlayViewComponent';
import OverlayViewContent from '../OverlayViewContent';
import mapOptions from './cfg';
import MapStyles from './map.scss';
const propTypes = {
getListings: PropTypes.func.isRequired,
updateMapState: PropTypes.func.isRequired,
visibleListings: PropTypes.array.isRequired,
};
class GoogleMap extends Component {
constructor() {
super();
this._onMapIdle = this._onMapIdle.bind(this);
this.onPlacesSearch = this.onPlacesSearch.bind(this);
}
_initMap(mapContainer) {
// Create a new map
this._map = new google.maps.Map(mapContainer, mapOptions);
this._bindOnMapIdleEvent();
};
_bindOnMapIdleEvent() {
// Attach idle event listener to the map
this._map.addListener('idle', this._onMapIdle);
}
_onMapIdle() {
const { updateMapState, getListings } = this.props;
if (this._searchedPlace) {
console.log('ON MAP IDLE');
let mapBounds = this._map.getBounds().toJSON();
updateMapState(mapBounds, this._searchedPlace);
getListings();
}
};
onPlacesSearch(searchedPlace) {
if (searchedPlace.name !== '' && searchedPlace.geometry !== null) {
// Clear out the old marker if present.
if (this._searchedPlaceMarker) {
this._searchedPlaceMarker.setMap(null);
this._searchedPlaceMarker = null;
}
let bounds = new google.maps.LatLngBounds();
// Create a marker for the searched place.
this._searchedPlaceMarker = new google.maps.Marker({
map: this._map,
title: searchedPlace.name,
position: searchedPlace.geometry.location
});
if (searchedPlace.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(searchedPlace.geometry.viewport);
} else {
bounds.extend(searchedPlace.geometry.location);
}
// Save currently searchedPlace into the class local variable
this._searchedPlace = searchedPlace;
// Set map so it contains the searchedPlace marker (Ideally it should be only one)
this._map.fitBounds(bounds);
} else {
return;
}
}
componentDidMount() {
// When component is mounted, initialise the map
this._initMap(this._mapContainer);
};
shouldComponentUpdate(nextProps) {
if (nextProps.visibleListings.length == this.props.visibleListings.length) {
return false;
} else {
return true;
}
};
componentWillUnmount() {
google.maps.event.clearInstanceListeners(this._map);
};
render() {
console.log('GOOGLEMAP RENDER');
return (
<div id="mapContainer">
<div id="mapCanvas" ref={(mapContainer) => this._mapContainer = mapContainer}></div>
<SearchBar onPlacesSearch={this.onPlacesSearch} />
{
this.props.visibleListings.map((listing, index) => {
return (
<OverlayViewComponent key={index} mapInstance={this._map} position={listing.geo_tag}>
<OverlayViewContent listingData={listing} />
</OverlayViewComponent>
);
})
}
</div>
);
}
};
GoogleMap.propTypes = propTypes;
export default GoogleMap;
OverlayView 组件
import React, { PropTypes, Component } from 'react';
import OverlayView from './utils/overlayViewHelpers';
const propTypes = {
position: PropTypes.array.isRequired,
mapInstance: PropTypes.object.isRequired,
};
class OverlayViewComponent extends Component {
componentDidMount() {
this._overlayInstance = new OverlayView(this.props.children, this.props.position, this.props.mapInstance);
};
componentWillUnmount() {
this._overlayInstance.setMap(null);
};
render() {
return null;
}
};
OverlayViewComponent.propTypes = propTypes;
export default OverlayViewComponent;
OverlayView 类
import ReactDOM from 'react-dom';
const EL_WIDTH = 30;
const EL_HEIGHT = 35;
function OverlayView(element, position, map) {
this.overlayContent = element;
this.point = new google.maps.LatLng(position[0], position[1]);
this.setMap(map);
}
OverlayView.prototype = Object.create(new google.maps.OverlayView());
OverlayView.prototype.constructor = OverlayView;
OverlayView.prototype.onAdd = function() {
console.log('onAdd');
this.containerElement = document.createElement('div');
this.containerElement.style.position = 'absolute';
this.containerElement.style.width = EL_WIDTH + 'px';
this.containerElement.style.height = EL_HEIGHT + 'px';
let panes = this.getPanes();
panes.overlayMouseTarget.appendChild(this.containerElement);
ReactDOM.render(this.overlayContent, this.containerElement);
};
OverlayView.prototype.draw = function() {
console.log('draw');
if (this.containerElement) {
let projection = this.getProjection();
let projectedLatLng = projection.fromLatLngToDivPixel(this.point);
console.log(projectedLatLng);
this.containerElement.style.top = projectedLatLng.y - EL_HEIGHT + 'px';
this.containerElement.style.left = projectedLatLng.x - Math.floor(EL_WIDTH / 2) + 'px';
}
};
OverlayView.prototype.onRemove = function() {
console.log('onRemove');
let parentEl = this.containerElement.parentNode;
parentEl.removeChild(this.containerElement);
ReactDOM.unmountComponentAtNode(this.containerElement);
};
export default OverlayView;
OverlayView 内容组件
import React, { PropTypes } from 'react';
import markerIcon from '../../../images/icon-marker.png';
const propTypes = {
listingData: PropTypes.object.isRequired,
};
const OverlayViewContent = (listingData) => {
console.log('OverlayViewContent render');
return (
<div className="customIcon">
<img src={markerIcon} title={listingData.where} />
</div>
);
};
OverlayViewContent.propTypes = propTypes;
export default OverlayViewContent;
【问题讨论】:
标签: javascript reactjs google-maps-api-3 redux