【问题标题】:Remove Zoom control from map in react-leaflet从反应传单中的地图中删除缩放控件
【发布时间】:2020-04-13 09:02:52
【问题描述】:

我正在构建一个 react-leaflet 应用程序,并且我试图将缩放控件与地图本身分开。这里提出了香草传单上下文中的相同问题:Placing controls outside map container with Leaflet?。这就是我试图在 react-leaflet 框架内完成的工作。这是我的项目的概要:

import UIWindow from './UIWindow'
import Map from './MapRL'

class App extends React.Component {
   render () {
      return (
         <div className="App">
            <Map />
            <UIWindow />
         </div>
      )
   }
}

export default App;

我的地图组件如下所示:

import React from 'react'
import { Map as LeafletMap, TileLayer } from 'react-leaflet'

class Map extends React.Component {

   render () {
      return(
         <LeafletMap
            className="sidebar-map"
            center={...}
            zoom={...}
            zoomControl={false}
            id="mapId" >

            <TileLayer
                url="..."
                attribution="...
            />

         </LeafletMap>
      )
   }
}

export default Map;

然后我的 UIWindow 是这样的:

class UIWindow extends React.Component {
   render () {
      return(
         <div className="UIWindow">
            <Header />
            <ControlLayer />
         </div>
      )
   }
}

最后,我的 ControlLayer(我希望 ZoomControl 存在的地方)应该如下所示:

class ControlLayer extends React.Component {
   render () {
      return (
         <div className="ControlLayer">
            <LeftSidebar />
            <ZoomControl />
            {this.props.infoPage && <InfoPage />}
         </div>
      )
   }
}

当然,对于当前的代码,将 ZoomControl 放在 ControlLayer 中会引发错误:TypeError: Cannot read property '_zoom' of undefined,更详细地记录了问题所在,以及所有参考资料传单关于缩放功能的内部代码。 (DomUtil.removeClass(this._zoomInButton, className);等)

我预计会出现错误,因为 ZoomControl 不再是 &lt;Map /&gt; 组件的子组件,而是 &lt;Map /&gt; 兄弟组件的孙子组件。我知道它的上下文提供者和消费者、LeafletProvider 和 LeafletConsumer 上的 react-leaflet 函数。当我尝试从&lt;ControlLayer /&gt; 中调用我的 LeafletConsumer 时,我什么也得不到。例如:

            <LeafletConsumer>
               {context => console.log(context)}
            </LeafletConsumer>

这会记录一个空对象。显然,我的 ControlLayer 中的 LeafletConsumer 没有正确连接到我的 &lt;Map /&gt; 中的 LeaflerProvider。我是否需要使用 LeafletProvider 以某种方式从地图中导出上下文?我对 React Context API 有点陌生,所以这对我来说还不直观。 (应用程序的大部分其余部分将使用 React Redux 来管理状态更改和组件之间的通信。这就是我计划将侧边栏的内容连接到地图本身的方式。我的侧边栏似乎没有任何问题与&lt;Map /&gt;完全断开)。

如何正确地将这个 ZoomControl 连接到我的地图组件?

更新:

我尝试在我的 redux 存储中捕获上下文,然后将其提供给我的外部化 ZoomControl。像这样:

            <LeafletConsumer>
               { context => {
                  this.props.captureContext(context)
               }}
            </LeafletConsumer>

这会将上下文捕获为我的 redux 存储的一部分。然后我将其用作新上下文中的值:

// ControlLayer.js

const MapContext = React.createContext()

            <MapContext.Provider value={this.props.mapContext}>
               <LeftSidebar />
               <MapContext.Consumer>
                  {context => {
                     if (context){
                        console.log(ZoomControl);

                     }
                  }}
               </MapContext.Consumer>
            </MapContext.Provider>

this.props.mapContext 是从我的 redux matchStateToProps 引入的,它正是 captureContext 函数捕获的上下文。

不过,这不起作用。我的反应开发工具显示,当 ZoomControl 位于地图组件中时,MapContent.Consumer 提供的值与反应传单固有的 '' 提供的值完全相同。但我仍然收到相同的错误消息。在这里很沮丧。

【问题讨论】:

    标签: javascript reactjs leaflet react-context react-leaflet


    【解决方案1】:

    这是没有钩子的相同方法:

    Provider 应该如下所示:

    class Provider extends Component {
      state = { map: null };
    
      setMap = map => {
        this.setState({ map });
      };
    
      render() {
        return (
          <Context.Provider value={{ map: this.state.map, setMap: this.setMap }}>
            {this.props.children}
          </Context.Provider>
        );
      }
    }
    

    传单组件将是:

    class Leaflet extends Component {
      mapRef = createRef(null);
    
      componentDidMount() {
        const map = this.mapRef.current.leafletElement;
        this.props.setMap(map);
      }
    
      render() {
        return (
          <Map
            style={{ width: "80vw", height: "60vh" }}
            ref={this.mapRef}
            center={[50.63, 13.047]}
            zoom={13}
            zoomControl={false}
            minZoom={3}
            maxZoom={18}
          >
            <TileLayer
              attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
              url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?"
            />
          </Map>
        );
      }
    }
    

    现在要将 setMap 函数访问到 compoenntDidMount,您需要执行以下操作:

    export default props => (
      <Context.Consumer>
        {({ setMap }) => <Leaflet {...props} setMap={setMap} />}
      </Context.Consumer>
    );
    

    其余的请看这里:Demo

    【讨论】:

    • 非常感谢您花时间写这篇文章。这对我来说更直观(至少在我还在学习钩子的时候)。据我所知,您在&lt;Map /&gt; 组件中使用this.props.setMap(map) 来设置Provider 的状态以保存对地图实例的引用。然后提供者将该引用传递给任何消费者......对吗?
    【解决方案2】:

    当您尝试将它放在地图包装器之外时,我不确定如何使用您的方法来实现这一点,其中 react-leaflet 的包装器 ZoomControl 不是地图包装器的子级。

    但是,对于像 ZoomControl 这样的小控件,一个简单的解决方案是创建一个与原始控件相同的自定义 Zoom 组件,使用本机 css 样式轻松构建它,并在访问地图元素后调用放大和分别输出方法。

    在下面的示例中,我使用 react-context 在地图加载后保存地图元素:

    useEffect(() => {
        const map = mapRef.current.leafletElement;
        setMap(map);
      }, [mapRef, setMap]);
    

    然后在这里使用地图引用来制作与原生相同的自定义缩放组件(css参见演示):

    const Zoom = () => {
      const { map } = useContext(Context);
    
      const zoomIn = e => {
        e.preventDefault();
        map.setZoom(map.getZoom() + 1);
      };
      const zoomOut = e => {
        e.preventDefault();
        map.setZoom(map.getZoom() - 1);
      };
    
      return (
        <div className="leaflet-bar">
          <a
            className="leaflet-control-zoom-in"
            href="/"
            title="Zoom in"
            role="button"
            aria-label="Zoom in"
            onClick={zoomIn}
          >
            +
          </a>
          <a
            className="leaflet-control-zoom-out"
            href="/"
            title="Zoom out"
            role="button"
            aria-label="Zoom out"
            onClick={zoomOut}
          >
            −
          </a>
        </div>
      );
    };
    

    然后把它放在你喜欢的地方:

    const App = () => {
      return (
        <Provider>
          <div style={{ display: "flex", flexDirection: "row" }}>
            <Leaflet />
            <Zoom />
          </div>
        </Provider>
      );
    };
    

    Demo

    【讨论】:

    • 这就是我想要的 100%,感谢您的周到回答。
    • 我对您的解决方案研究得越多,就越意识到我并没有完全理解它。部分原因是因为我刚刚学习钩子。您介意解释一下const { setMap } = useContext(Context) 行(Leaflet.jsx 中的第 10 行)吗? setMap 函数是如何成为上下文对象的一部分的?我也不太了解您的 useEffect 钩子如何与您的 const mapRef = useRef(null) 线交互。你的解决方案可以不用钩子编写吗? (我知道这是一项艰巨的任务,我感谢您在答案中付出的努力)
    • setMap(map); 是在组件使用上下文 api 加载时使用地图实例更新地图上下文的函数。 mapRef 用于保存对地图的引用,因此在 Leaflet 组件加载时使用并更新。
    • 我在没有使用钩子的情况下提供了另一个答案。如果对你有帮助,请点赞
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-03
    • 1970-01-01
    • 2015-03-24
    • 2014-05-24
    • 2020-07-13
    • 1970-01-01
    相关资源
    最近更新 更多