Убрать элемент управления масштабированием с карты в листовке React

Я создаю приложение-буклет, и я пытаюсь отделить элемент управления масштабированием от самой карты. Тот же вопрос в контексте ванильного Leaflet был задан здесь: Размещение элементов управления вне карты контейнер с листовкой?. Это то, что я пытаюсь достичь в рамках системы response-листовок. Вот общий план моего проекта:

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: невозможно прочитать свойство _zoom из undefined, с более подробным описанием того, что не так, со всеми ссылками на внутренний лист Leaflet. код, касающийся функции масштабирования. (DomUtil.removeClass(this._zoomInButton, className); и т. Д.)

Я ожидал ошибки, потому что ZoomControl больше не является дочерним элементом компонента <Map />, а скорее является внуком брата или сестры <Map />. Я знаю функции response-leaflet по его поставщику контекста и потребителю, LeafletProvider и LeafletConsumer. Когда я пытаюсь вызвать LeafletConsumer из своего <ControlLayer />, я ничего не получаю. Например:

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

Это регистрирует пустой объект. Ясно, что мой LeafletConsumer из моего ControlLayer неправильно подключен к LeaflerProvider из моего <Map />. Нужно ли мне каким-то образом экспортировать контекст с карты с помощью LeafletProvider? Я немного новичок в React Context API, поэтому мне это пока не интуитивно понятно. (Большая часть остальной части приложения будет использовать React Redux для управления изменениями состояния и обмена данными между компонентами. Именно так я планирую подключить содержимое боковой панели к самой карте. У моей боковой панели нет никаких проблем при полном отключении от <Map />).

Как я могу правильно подключить этот 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.

Тем не менее, это не работает. Мои инструменты для разработки реакции показывают, что MapContent.Consumer дает те же значения, что и «присущий» response-листовки, когда ZoomControl находится внутри компонента Map. Но я все равно получаю то же сообщение об ошибке. Здесь очень расстроен.


person Seth Lutske    schedule 20.12.2019    source источник


Ответы (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 для compoenntDidMount, вам необходимо сделать следующее:

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

Остальное см. Здесь: Демо

person kboul    schedule 16.01.2020
comment
Большое спасибо за то, что нашли время написать это. Для меня это более интуитивно понятно (по крайней мере, пока я все еще изучаю хуки). Насколько я понимаю, вы используете this.props.setMap(map) в компоненте <Map />, чтобы установить состояние Provider для хранения ссылки на экземпляр карты. Затем поставщик передает эту ссылку любому потребителю ... верно? - person Seth Lutske; 17.01.2020

Я не уверен, как добиться этого, используя ваш подход, когда оболочка ZoomControl для response-листовки не является дочерним элементом оболочки Map, когда вы пытаетесь разместить ее за пределами оболочки Map.

Однако для небольшого элемента управления, такого как ZoomControl, простым решением было бы создать настраиваемый компонент Zoom, идентичный оригиналу, легко сконструировать его с использованием собственного стиля css и после доступа к элементу карты вызывать методы увеличения и уменьшения соответственно .

В приведенном ниже примере я использую response-context для сохранения элемента карты после загрузки карты:

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

а затем используйте ссылку на карту, чтобы сделать настраиваемый компонент Zoom идентичным нативному (для 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>
  );
};

Демо

person kboul    schedule 23.12.2019
comment
Это на 100% то, что я искал, спасибо за вдумчивый ответ. - person Seth Lutske; 23.12.2019
comment
Чем больше я изучаю ваше решение, тем больше понимаю, что не совсем понимаю его. Частично это потому, что я только сейчас изучаю хуки. Не могли бы вы объяснить строку const { setMap } = useContext(Context) (строка 10 в Leaflet.jsx)? Как функция setMap стала частью объекта контекста? Я также не совсем понимаю, как ваш useEffect хук взаимодействует с вашей const mapRef = useRef(null) строкой. Можно ли написать ваше решение без хуков? (Я знаю, что это сложная задача, я ценю усилия, которые вы вложили в ответ) - person Seth Lutske; 16.01.2020
comment
setMap(map); - это функция, которая обновляет контекст карты с помощью экземпляра карты, когда компонент загружается с использованием context api. mapRef используется для хранения ссылки на карту, поэтому используется и обновляется при загрузке компонента Leaflet. - person kboul; 17.01.2020
comment
Я дал другой ответ без использования крючков. Пожалуйста, проголосуйте за него, если это вам поможет - person kboul; 17.01.2020