从react传单中的地图中删除缩放控制

IT技术 javascript reactjs leaflet react-context react-leaflet
2021-05-12 00:27:02

我正在构建一个 react-leaflet 应用程序,并且我试图将缩放控件与地图本身分开。这里在原版传单上下文中提出了相同的问题:Place 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,有一些更详细的错误说明,以及关于缩放功能的 Leaflet 内部代码的所有参考。DomUtil.removeClass(this._zoomInButton, className);等)

我预计会出现错误,因为 ZoomControl 不再是该<Map />组件的子组件,而是其<Map />兄弟组件的孙子组件我知道 react-leaflet 在其上下文提供者和消费者 LeafletProvider 和 LeafletConsumer 上的功能。当我尝试从我的 内部调用我的 LeafletConsumer 时<ControlLayer />,我一无所获。例如:

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

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

如何正确地将此 ZoomControl 连接到我的 Map 组件?

更新:

我尝试在我的 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>

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

尽管如此,这还是行不通。我的react开发工具显示 MapContent.Consumer 给出的值与当 ZoomControl 位于 Map 组件内时 react-leaflet 的固有 '' 给出的值完全相同。但我仍然收到相同的错误消息。在这里非常沮丧。

2个回答

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

提供者应如下所示:

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 函数访问到 componentDidMount,您需要执行以下操作:

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

其余的请看这里:演示

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

但是,对于像 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>
  );
};

演示