Реализация динамического JSX-элемента в маркере с помощью response-leaflet

У меня есть приложение React, в котором я использую Leaflet через react-leaflet, обе суперполезные библиотеки.

В этом приложении у меня есть группа координат, которые необходимо отобразить следующим образом:

  1. При уменьшении масштабируйте координаты в кластеры маркеров, например,  введите описание изображения здесь

  2. При увеличении каждый маркер должен иметь

    1. A dynamic countdown timer under it
    2. Динамические часы обратного отсчета SVG вокруг него, например,  введите описание изображения здесь

Для кластеризации я использую плагин response-leaflet-markercluster, который работает отлично подходит для показа статического контента.

Но когда мне нужно показать какой-либо динамический контент в каждом маркере, у меня нет возможности отправить JSX, есть только положение для статического HTML, как видно из доступного примера здесь.

// Template for getting popup html MarkerClusterGroup
// IMPORTANT: that function returns string, not JSX
function getStringPopup(name) {
  return (`
    <div>
      <b>Hello world!</b>
      <p>I am a ${name} popup.</p>
    </div>
  `);
}

// that function returns Leaflet.Popup
function getLeafletPopup(name) {
  return L.popup({ minWidth: 200, closeButton: false })
    .setContent(`
      <div>
        <b>Hello world!</b>
        <p>I am a ${name} popup.</p>
      </div>
    `);
}

Есть ли способ справиться с этой ситуацией? Как я могу сделать маркер JSX вместо статического маркера HTML?

PS: Я пробовал использовать ReactDOM.renderToString уже, но это уродливый хакер и каждый раз требует повторного рендеринга маркеров.

ТИА !!

Вот образец WebpackBin, с которым можно поиграть, если у вас есть решение.


person nikjohn    schedule 30.10.2017    source источник
comment
похоже на дублированный заголовок stackoverflow.com/questions/37079847/   -  person Vlad    schedule 30.10.2017
comment
@Vlad На самом деле, я не собираюсь использовать renderToString как решение. Я хочу использовать здесь JSX, а не статический HTML   -  person nikjohn    schedule 30.10.2017
comment
Так почему бы не отрендерить jsx в скрытый (с css) html, а затем передать его как .innerHTML?   -  person Vlad    schedule 30.10.2017
comment
@Vlad Не могли бы вы обновить предоставленную корзину, чтобы показать, что вы имеете в виду? Я не совсем понял это   -  person nikjohn    schedule 30.10.2017
comment
О, я имел в виду ответ на вопрос, на который я разместил ссылку выше   -  person Vlad    schedule 30.10.2017
comment
Я тоже ищу решение этой проблемы   -  person Tim McIntire    schedule 15.03.2018
comment
Это может быть решением: jahed.io/2018/03/20 / react-portals-and-leaflet Но я понятия не имею, как им пользоваться. Может, кто-нибудь сможет в этом разобраться   -  person Benjamin M    schedule 03.02.2019


Ответы (1)


Теперь я придумал рабочий код для рендеринга пользовательского JSX в качестве маркера.

Это 95% копия https://jahed.io/2018/03/20/react-portals-and-leaflet/ и 5% вдохновения от https://github.com/PaulLeCam/react-leaflet/blob/master/src/Marker.js

Я уверен, что некоторые вещи можно еще оптимизировать.

import * as React from 'react';
import { createPortal } from "react-dom";
import { DivIcon, marker } from "leaflet";
import * as RL from "react-leaflet";
import { MapLayer } from "react-leaflet";
import { difference } from "lodash";

const CustomMarker = (RL as any).withLeaflet(class extends MapLayer<any> {
    leafletElement: any;
    contextValue: any;

    createLeafletElement(props: any) {
        const { map, layerContainer, position, ...rest } = props;

// when not providing className, the element's background is a white square
// when not providing iconSize, the element will be 12x12 pixels
        const icon = new DivIcon({ ...rest, className: '', iconSize: undefined });

        const el = marker(position, { icon: icon, ...rest });
        this.contextValue = { ...props.leaflet, popupContainer: el };
        return el;
    }

    updateLeafletElement(fromProps: any, toProps: any) {
        const { position: fromPosition, zIndexOffset: fromZIndexOffset, opacity: fromOpacity, draggable: fromDraggable, className: fromClassName } = fromProps;
        const { position: toPosition, zIndexOffset: toZIndexOffset, toOpacity, draggable: toDraggable, className: toClassName } = toProps;

        if(toPosition !== fromPosition) {
            this.leafletElement.setLatLng(toPosition);
        }
        if(toZIndexOffset !== fromZIndexOffset) {
            this.leafletElement.setZIndexOffset(toZIndexOffset);
        }
        if(toOpacity !== fromOpacity) {
            this.leafletElement.setOpacity(toOpacity);
        }
        if(toDraggable !== fromDraggable) {
            if(toDraggable) {
                this.leafletElement.dragging.enable();
            } else {
                this.leafletElement.dragging.disable();
            }
        }
        if(toClassName !== fromClassName) {
            const fromClasses = fromClassName.split(" ");
            const toClasses = toClassName.split(" ");
            this.leafletElement._icon.classList.remove(
                ...difference(fromClasses, toClasses)
            );
            this.leafletElement._icon.classList.add(
                ...difference(toClasses, fromClasses)
            );
        }
    }

    componentWillMount() {
        if(super.componentWillMount) {
            super.componentWillMount();
        }
        this.leafletElement = this.createLeafletElement(this.props);
        this.leafletElement.on("add", () => this.forceUpdate());
    }

    componentDidUpdate(fromProps: any) {
        this.updateLeafletElement(fromProps, this.props);
    }

    render() {
        const { children } = this.props;
        const container = this.leafletElement._icon;

        if(!container) {
            return null;
        }

        const portal = createPortal(children, container);

        const LeafletProvider = (RL as any).LeafletProvider;

        return children == null || portal == null || this.contextValue == null ? null : (
            <LeafletProvider value={this.contextValue}>{portal}</LeafletProvider>
        )
    }
});

А затем просто используйте его в своем компоненте следующим образом:

<Map ...>
  <CustomMarker position={[50, 10]}>
    <Tooltip>
      tooltip
    </Tooltip>
    <Popup>
      popup
    </Popup>

    <div style={{ backgroundColor: 'red' }} onClick={() => console.log("CLICK")}>
      CUSTOM MARKER CONTENT
    </div>
    MORE CONTENT
  </CustomMarker>
</Map>

Если вы не используете TypeScript. Просто удалите вещи as any и : any.


РЕДАКТИРОВАТЬ: что-то автоматически устанавливает width: 12px; и height: 12px;. Я пока не знаю, как этого избежать. Все остальное вроде нормально работает!

EDIT2: Исправлено! Использование iconSize: undefined


EDIT3: также есть это: https://github.com/OpenGov/react-leaflet-marker-layer Не тестировал, но код примера выглядит неплохо.

person Benjamin M    schedule 03.02.2019