首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >React-Leaflet:将地图控件组件放置在地图之外?

React-Leaflet:将地图控件组件放置在地图之外?
EN

Stack Overflow用户
提问于 2020-01-15 07:07:21
回答 3查看 4K关注 0票数 2

这是我的另一个问题的更广义的版本:Remove Zoom control from map in react-leaflet。React-leaflet附带了一些控制地图的组件-即<ZoomControl /><LayersControl />等。但为了让这些组件与地图实例正确通信,它们必须作为<Map />组件的子组件编写,如下所示:

代码语言:javascript
复制
<Map center={...} zoom={...}>

  <ZoomControl />
  <LayersControl />
  <MyCustomComponent />

</Map>

我试图创建的是这样一种情况,即不是map的直接子组件的map组件可以正确地与map进行通信。例如:

代码语言:javascript
复制
<App>
  <Map />
  <Sibling>
    <Niece>
      <ZoomControl />
      <OtherControls />
    </Niece>
  </Sibling>
</App>

显然,这里的问题是,当这些控件不再是<Map />的子控件或后代控件时,它们不会通过提供程序接收地图实例。正如您在my other question中所看到的,我尝试创建了一个新的上下文对象,并使用它来为被替换的控件提供映射。这不管用,我也不知道为什么。到目前为止,我的解决方案是使用vanilla javascript将这些控件迁移到它们预期父级的componentDidMount中,如下所示:

代码语言:javascript
复制
// Niece.js
componentDidMount(){
  const newZoomHome = document.querySelector('#Niece')
  const leafletZoomControl= document.querySelector('.leaflet-control-zoom')
  newZoomHome.appendChild(leafletZoomControl)
}

我真的很讨厌这一点,因为现在我的通用组件结构不能反映应用程序结构。我的Zoom需要写成地图的一部分,但最终会出现在我的Neice组件中。

Kboul在我的另一个问题中的解决方案是简单地从头开始重新构建缩放组件,并将地图上下文提供给它。这对于一个简单的缩放组件很有效,但是对于更复杂的组件,我不能重建整个框架。例如,我创建了一个esri-leaflet's geosearch的快速react-leaflet组件版本

代码语言:javascript
复制
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);

它相对简单,在<Map />组件中声明时效果很好。但我想将其移动到应用程序中的一个单独位置,并且我不想对整个esri地理搜索进行重新编码。如何在<Map />组件之外使用正常运行的react-leaflet控件组件,同时正确地将其链接到地图实例?

如果你对此感兴趣,这里有一个快速的codsandbox template可以开始摆弄。感谢您的阅读。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2020-01-17 06:47:52

您可以使用onAdd方法在地图外部为您的插件创建一个容器,然后使用ref将元素添加到DOM中,如下所示:

代码语言:javascript
复制
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

票数 2
EN

Stack Overflow用户

发布于 2020-01-21 02:40:02

感谢kboul在thismy other question中的回答,我将在这里写出一个答案。这真的是kboul的答案,但我想通过把它写出来,让它在我的大脑中牢牢记住,并让任何偶然经过的人都能得到它。

首先,我们需要创建一个上下文对象,并为该上下文创建一个提供者。我们将在目录中创建两个新文件,以便于从其他文件访问:

代码语言:javascript
复制
/src
  -App.js
  -Map.js
  -MapContext.js
  -MapProvider.js
  -Sibling.js
  -Niece.js
  /Components
    -ZoomControl.js
    -OtherControls.js

创建一个空的上下文对象:

代码语言:javascript
复制
// MapContext.jsx
import { createContext } from "react";

const MapContext = createContext();

export default MapContext

接下来,我们使用MapContext对象创建一个MapProvider。MapProvider有自己的状态,其中包含一个空白引用,该引用将成为映射引用。它还有一个方法setMap,用于在其状态内设置地图引用。它提供空白引用作为其值,以及设置映射引用的方法。最后,它呈现它的子对象:

代码语言:javascript
复制
// 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中的地图。

代码语言:javascript
复制
// 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

在最后一步中,我们不导出地图,而是导出包装在provider中的地图,并将MapProvider的{value}作为地图的道具。这样,当在componentDidMount上的App组件中调用LeafletMap时,setMap函数将作为一个属性被调用,并回调到MapProvider setMap函数。这会将MapProvider的状态设置为具有对映射的引用。但是,只有在App中渲染地图时,才会发生这种情况

代码语言:javascript
复制
// App.js

class App extends React.Component{

  state = { mapLoaded: false }

  componentDidMount(){
    this.setState({ mapLoaded:true })

  }

  render(){
    return (
      <MapProvider>
        <LeafletMap  />
        {this.state.mapLoaded && <Sibling/>}
      </MapProvider>
    )
  }

}

请注意,在触发LeafletMap componentDidMount之前,不会调用MapProvider的setMap方法。因此,在App中呈现时,还没有上下文值,Sibling中任何试图访问上下文的组件都还没有上下文值。但是,一旦App的render运行,并且LeafletMaps componentDidMount运行,setMap就会运行,并且提供程序的map值可用。所以在App中,我们一直等到componentDidMount运行,这时setMap已经运行了。我们在App中设置了地图加载的状态,Sibling的条件render语句将呈现Sibling及其所有子对象,其中MapContext对象正确地引用了地图。现在我们可以在组件中使用它。例如,我重写了GeoSearch组件,使其工作方式如下(多亏了kboul的建议):

代码语言:javascript
复制
// 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;

因此,我们在这里所做的就是创建一个Esri GeoSearch对象,并将其HTML和相关的处理程序存储在一个变量searchContainer中,但没有将其添加到地图中。相反,我们在DOM树中想要的位置创建一个容器div,然后在componentDidMount上,我们在该容器div中呈现该HTML.因此,我们有一个组件,它是在应用程序中的预定位置编写和呈现的,它可以正确地与地图通信。

很抱歉读了这么长时间,但我想写出答案来巩固我自己的知识,并为将来可能发现自己处于同样情况的任何人提供一个相当规范的答案。功劳100%归功于科布,我只是把他的答案综合到一个地方。他有一个有效的例子here。如果你喜欢这个答案,请支持他的回答。

票数 2
EN

Stack Overflow用户

发布于 2020-06-02 22:37:17

在这种情况下可能没有什么帮助,但我使用redux定义了map的状态,然后使用正常操作和缩减程序从应用程序中的任何位置更新map。

因此,您的操作将如下所示

代码语言:javascript
复制
export const setCenterMap = (payload) => ({
  type: CENTER_MAP,
  payload,
})

和一个基本的reducer:

代码语言:javascript
复制
const initialState = {
centerMap: false,
}

export const reducer = (state = initialState, action) => {
    switch (action.type) {
        case (CENTER_MAP) : {
            return ({
                ...state,
                centerMap: action.payload
            })
        }
        default: return state
    }
}

然后将其连接到地图组件

代码语言:javascript
复制
const mapStateToProps = state => ({
  centerMap: state.app.centerMap,
})

const mapDispatchToProps = dispatch => ({
  setCenterMap: (centerMap) => dispatch(setCenterMap(centerMap)),
})

现在,您可以在Leaflet组件外部操作贴图。

代码语言:javascript
复制
        <LeafletMap
            center={centerMap}
            sites={event.sites && [...event.sites, ...event.workingGroups]}
        />
        <button onClick={() => setCenterMap([5.233, 3.342])} >SET MAP CENTER</button>

其中大部分是伪代码,所以你必须自己使用它,但我发现从LeafletMap组件外部添加一些基本的地图控件是一种相当轻松的方法,特别是如果你已经在使用redux的话。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/59743050

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档