这是我另一个问题的更概括的版本:Remove Zoom control from map in react-leaflet。 React-leaflet带有一些控制地图的组件,即<ZoomControl />
,<LayersControl />
等。但是,为了使这些组件与地图实例正确通信,必须将它们写为[ C0]组件,如下所示:
<Map />
我正在尝试创建的情况是,不是地图直接子级的地图组件可以与地图正确通信。例如:
<Map center={...} zoom={...}>
<ZoomControl />
<LayersControl />
<MyCustomComponent />
</Map>
显然,这里的麻烦是,当这些控件不再是<App>
<Map />
<Sibling>
<Niece>
<ZoomControl />
<OtherControls />
</Niece>
</Sibling>
</App>
的子代或子代时,它们不会通过提供程序接收地图实例。如您在<Map />
中所见,我尝试创建一个新的Context对象,并使用它来提供到已替换控件的映射。那没有用,我不确定为什么。到目前为止,我的解决方案是使用香草javascript在这些控件的预期父控件的my other question中重新放置它们,例如:
componentDidMount
我真的很讨厌这一点,因为现在我的常规组件结构无法反映应用程序结构。 “我的缩放”需要作为我的地图的一部分编写,但最终要放在我的Neice组件中。
Kboul在我的另一个问题中的解决方案只是从头开始重建缩放组件并将其提供给地图上下文。这对于简单的缩放组件来说效果很好,但对于更复杂的组件,我无法重建整个框架。例如,我制作了// Niece.js
componentDidMount(){
const newZoomHome = document.querySelector('#Niece')
const leafletZoomControl= document.querySelector('.leaflet-control-zoom')
newZoomHome.appendChild(leafletZoomControl)
}
的快速反应叶组件版本:
esri-leaflet's geosearch
它相对简单,并且在import { withLeaflet, MapControl } from "react-leaflet";
import * as ELG from "esri-leaflet-geocoder";
class GeoSearch extends MapControl {
createLeafletElement(props) {
const searchOptions = {
...props,
providers: props.providers ? props.providers.map( provider => ELG[provider]()) : null
};
const GeoSearch = new ELG.Geosearch(searchOptions);
// Author can specify a callback function for the results event
if (props.onResult){
GeoSearch.addEventListener('results', props.onResult)
}
return GeoSearch;
}
componentDidMount() {
const { map } = this.props.leaflet;
this.leafletElement.addTo(map);
}
}
export default withLeaflet(GeoSearch);
组件内声明时效果很好。但是我想将其移动到应用程序中的单独位置,并且我不需要重新编码整个esri Geosearch。在正确将其链接到地图实例时,如何在<Map />
组件之外使用功能正常的react-leaflet控制组件?
如果您愿意的话,这里是一个快速开始的<Map />
。感谢您的阅读。
您可以使用codsandbox template方法在地图外为您的插件创建一个容器,然后使用ref像这样将元素添加到DOM:
onAdd
class Map extends React.Component {
mapRef = createRef();
plugin = createRef();
componentDidMount() {
// map instance
const map = this.mapRef.current.leafletElement;
const searchcontrol = new ELG.Geosearch();
const results = new L.LayerGroup().addTo(map);
searchcontrol.on("results", function(data) {
results.clearLayers();
for (let i = data.results.length - 1; i >= 0; i--) {
results.addLayer(L.marker(data.results[i].latlng));
}
});
const searchContainer = searchcontrol.onAdd(map);
this.plugin.current.appendChild(searchContainer);
}
render() {
return (
<>
<LeafletMap
zoom={4}
style={{ height: "50vh" }}
ref={this.mapRef}
center={[33.852169, -100.5322]}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
</LeafletMap>
<div ref={this.plugin} />
</>
);
}
}
[感谢Demo和this中的kboul的答案,我将在这里写出答案。这确实是kboul的答案,但我想通过写下来将其牢牢扎在我的大脑中,并让所有迷路的人都可以使用。
首先,我们需要创建一个上下文对象,以及该上下文的提供者。我们将在目录中创建两个新文件,以方便其他文件的访问:
my other question
创建一个空的上下文对象:
/src
-App.js
-Map.js
-MapContext.js
-MapProvider.js
-Sibling.js
-Niece.js
/Components
-ZoomControl.js
-OtherControls.js
[接下来,我们使用MapContext对象创建MapProvider。 MapProvider具有其自己的状态,该状态包含一个空白引用,该引用将成为地图引用。它还具有方法// MapContext.jsx
import { createContext } from "react";
const MapContext = createContext();
export default MapContext
,以在其状态内设置地图参考。它提供空白参考值以及设置地图参考值的方法作为其值。最后,它呈现其子级:
setMap
现在,在Map组件中,我们将导出包装在MapProvider中的地图。
// MapProvider.jsx
import React from "react";
import MapContext from "./MapContext";
class MapProvider extends React.Component {
state = { map: null };
setMap = map => {
this.setState({ map });
};
render() {
return (
<MapContext.Provider value={{ map: this.state.map, setMap: this.setMap }}>
{this.props.children}
</MapContext.Provider>
);
}
}
export default MapProvider;
[在最后一步中,我们不导出地图,而是导出包装在提供程序中的地图,并以MapProvider的// Map.jsx
import React from "react";
import { Map as MapComponent, TileLayer, Marker, etc } from 'react-leaflet'
import MapContext from './MapContext'
class Map extends React.Component{
mapRef = React.createRef(null);
componentDidMount() {
const map = this.mapRef.current.leafletElement;
this.props.setMap(map);
}
render(){
return (
<MapComponent
center={[centerLat, centerLng]}
zoom={11.5}
...all the props
ref={this.mapRef} >
</MapComponent>
);
}
}
const LeafletMap = props => (
<MapContext.Consumer>
{({ setMap }) => <Map {...props} setMap={setMap} />}
</MapContext.Consumer>
)
export default LeafletMap
作为地图的道具。这样,当在componentDidMount上的{value}
组件中调用LeafletMap
时,App
函数将作为道具被调用,并返回到setMap
setMap函数。这会将MapProvider
的状态设置为具有对地图的引用。但这不会发生,直到在MapProvider
中渲染地图:
App
注意,在// App.js
class App extends React.Component{
state = { mapLoaded: false }
componentDidMount(){
this.setState({ mapLoaded:true })
}
render(){
return (
<MapProvider>
<LeafletMap />
{this.state.mapLoaded && <Sibling/>}
</MapProvider>
)
}
}
componentDidMount触发之前,不会调用MapProvider的setMap
方法。因此,在LeafletMap
中进行渲染时,还没有上下文值,并且App
中尝试访问该上下文的任何组件都没有该值。但是一旦Sibling
的渲染运行并且App
componentDidMount运行,LeafletMaps
运行,并且setMap
值是提供者就可用。因此,在map
中,我们等待,直到componentDidMount运行,此时App
已经运行。我们在setMap
中设置加载地图的状态,App
的条件渲染语句将使用其所有子级渲染Sibling
,并使用MapContext对象正确引用地图。现在我们可以在组件中使用它。例如,我重写了GeoSearch组件,使其像这样工作(感谢kboul的建议):
Sibling
所以我们在这里所做的就是创建一个Esri GeoSearch对象,并将其HTML和关联的处理程序存储在变量// GeoSearch
import React from 'react'
import MapContext from '../Context'
import * as ELG from "esri-leaflet-geocoder";
class EsriGeoSearch extends React.Component {
componentDidMount() {
const map = this.mapReference
const searchOptions = {
...this.props,
providers: this.props.providers ? this.props.providers.map( provider => ELG[provider]()) : null
};
const GeoSearch = new ELG.Geosearch(searchOptions);
const searchContainer = GeoSearch.onAdd(map);
document.querySelector('geocoder-control-wrapper').appendChild(searchContainer);
}
render() {
return (
<MapContext.Consumer>
{ ({map}) => {
this.mapReference = map
return <div className='geocoder-control-wrapper' EsriGeoSearch`} />
}}
</MapContext.Consumer>
)
}
}
export default EsriGeoSearch;
中,但没有将其添加到地图中。而是在DOM树中的所需位置创建一个容器div,然后在componentDidMount上,在该容器div中渲染HTML。因此,我们有一个在应用程序的预期位置编写和渲染的组件,该组件可以与地图正确通信。
很长的阅读时间,很抱歉,但是我想写出答案来巩固自己的知识,并为将来可能遇到相同情况的任何人提供一个相当规范的答案。功劳100%归功于kboul,我只是将他的答案综合到一个地方。他有一个可行的示例searchContainer
。如果您喜欢这个答案,请给他的答案增个词。