Работа с React | MapGL | 2GIS Documentation
MapGL JS API

Работа с React

При использовании MapGL с React нужно учитывать две особенности:

  1. Повторный рендеринг карты.
  2. Получение доступа к карте из других компонентов.

Возьмём для примера простое приложение на React:

import React from 'react';
import ReactDOM from 'react-dom';

import { App } from './App';

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

С простым компонентом App:

import React from 'react';

export const App = () => {
    return <div>My App</div>;
};

Чтобы добавить в это приложение карту, создадим новый компонент:

import { load } from '@2gis/mapgl';

export const Map = () => {
    useEffect(() => {
        let map;
        load().then((mapglAPI) => {
            map = new mapglAPI.Map('map-container', {
                center: [55.31878, 25.23584],
                zoom: 13,
                key: 'Your API access key',
            });
        });

        // Удаляем карту при размонтировании компонента
        return () => map && map.destroy();
    }, []);

    return (
        <div style={{ width: '100%', height: '100%' }}>
            <MapWrapper />
        </div>
    );
};

Обратите внимание, что в этом компоненте нет контейнера карты (map-container). Чтобы избежать повторного рендеринга карты, мы создадим отдельный компонент, который будет использовать React.memo:

const MapWrapper = React.memo(
    () => {
        return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
    },
    () => true,
);

Второй аргумент React.memo - функция, которая определяет, можно ли использовать последний результат рендеринга, избегая таким образом повторного рендеринга. В нашем случае эта функция будет всегда возвращать true.

Осталось добавить карту в компонент App и всё готово к работе:

export const App = () => {
    return (
        <div style={{ width: '100%', height: 400 }}>
            <Map />
        </div>
    );
};

Полный исходный код можно найти в примере ниже. Не забудьте указать "Babel" в качестве препроцессора JavaScript, если вы пытаетесь запустить пример на CodePen.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>2GIS Map API</title>
        <meta name="description" content="Example of mapgl initialization in React application" />
        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <!-- Don't use this in production: -->
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
        <style>
            html,
            body,
            #root {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="root" style="height: 100%;"></div>
        <script type="text/babel">
            // If you view this demo in jsfiddle do not forget to change language in js panel from "javascript" to "Babel + JSX"
            const MapWrapper = React.memo(
                () => {
                    return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
                },
                () => true,
            );
            const MapGL = () => {
                React.useEffect(() => {
                    const map = new mapgl.Map('map-container', {
                        center: [55.31878, 25.23584],
                        zoom: 13,
                        key: 'Your API access key',
                    });

                    // Destroy the map, if Map component is going to be unmounted
                    return () => map.destroy();
                }, []);

                return (
                    <div style={{ width: '100%', height: '100%' }}>
                        <MapWrapper />
                    </div>
                );
            };
            const App = () => {
                return (
                    <div style={{ width: '100%', height: '100%' }}>
                        <MapGL />
                    </div>
                );
            };

            ReactDOM.render(<App />, document.getElementById('root'));
        </script>
    </body>
</html>

Второй частый сценарий использования - получение доступа к карте из другого компонента.

В качестве примера можно взять кнопку, которая меняет центр карты. Если эта кнопка будет расположена в отдельном компоненте, то нужно из этого компонента получить доступ к компоненту с картой. Для этого можно использовать React Context API.

Для начала создадим новый компонент и вызовем React.createContext(), чтобы создать объект Context.

const MapContext = React.createContext([undefined, () => {}]);
const MapProvider = (props) => {
    const [mapInstance, setMapInstance] = React.useState();

    return (
        <MapContext.Provider value={[mapInstance, setMapInstance]}>
            {props.children}
        </MapContext.Provider>
    );
};

Затем обернём компонент App компонентом MapProvider. Это позволит использовать созданный объект Context в компоненте App и всех дочерних компонентах.

ReactDOM.render(
    <MapProvider>
        <App />
    </MapProvider>,
    rootElement,
);

Теперь после создания карты мы можем использовать Context, чтобы сохранить ссылку на карту:

export const Map = () => {
    const [_, setMapInstance] = React.useContext(MapContext);

    useEffect(() => {
        let map;
        load().then((mapglAPI) => {
            map = new mapglAPI.Map('map-container', {
                center: [55.31878, 25.23584],
                zoom: 13,
                key: 'Your API access key',
            });
        });
        // Сохраняем ссылку на карту
        setMapInstance(map);
        // Удаляем карту при размонтировании компонента
        return () => map && map.destroy();
    }, []);

    return (
        <div style={{ width: '100%', height: '100%' }}>
            <MapWrapper />
        </div>
    );
};

Сохранённую ссылку можно использовать в других компонентах приложения. Например, создадим кнопку, которая будет менять центр карты:

export const MoveMapButton = () => {
    const [mapInstance] = React.useContext(MapContext);

    const setInitialCenter = useCallback(() => {
        if (mapInstance) {
            mapInstance.setCenter([55.31878, 25.23584]);
        }
    }, [mapInstance]);

    return <button onClick={setInitialCenter}>Set initial center</button>;
};

Полный исходный код можно найти в примере ниже. Не забудьте указать "Babel" в качестве препроцессора JavaScript, если вы пытаетесь запустить пример на CodePen.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>2GIS Map API</title>
        <meta
            name="description"
            content="Example of interactions with a map in React application"
        />
        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <!-- Don't use this in production: -->
        <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
        <style>
            html,
            body,
            #root {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="root"></div>
        <script type="text/babel">
            // If you view this demo in jsfiddle do not forget to change language in js panel from "javascript" to "Babel + JSX"
            const MapContext = React.createContext([undefined, () => {}]);

            const MapProvider = (props) => {
                const [mapInstance, setMapInstance] = React.useState();

                return (
                    <MapContext.Provider value={[mapInstance, setMapInstance]}>
                        {props.children}
                    </MapContext.Provider>
                );
            };

            const MapWrapper = React.memo(
                () => {
                    return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
                },
                () => true,
            );
            const MapGL = () => {
                const [_, setMapInstance] = React.useContext(MapContext);

                React.useEffect(() => {
                    const map = new mapgl.Map('map-container', {
                        center: [55.31878, 25.23584],
                        zoom: 13,
                        key: 'Your API access key',
                    });

                    setMapInstance(map);

                    // Destroy the map, if Map component is going to be unmounted
                    return () => map.destroy();
                }, []);

                return (
                    <div style={{ width: '100%', height: '100%' }}>
                        <MapWrapper />
                    </div>
                );
            };
            const MoveMapButton = () => {
                const [mapInstance] = React.useContext(MapContext);

                const setInitialCenter = React.useCallback(() => {
                    if (mapInstance) {
                        mapInstance.setCenter([55.31878, 25.23584]);
                    }
                }, [mapInstance]);

                return (
                    <button style={{ padding: '4px 10px' }} onClick={setInitialCenter}>
                        Set initial center
                    </button>
                );
            };
            const App = () => {
                return (
                    <div>
                        <div style={{ padding: '0 0 6px' }}>
                            Drag the map and then click the button <MoveMapButton />
                        </div>
                        <br />
                        <div style={{ width: '100%', height: 250 }}>
                            <MapGL />
                        </div>
                    </div>
                );
            };

            ReactDOM.render(
                <MapProvider>
                    <App />
                </MapProvider>,
                document.getElementById('root'),
            );
        </script>
    </body>
</html>

Если вы используете npm, вы можете скачать пакет @2gis/mapgl, который также включает поддержку TypeScript.

Примеры использования и другую информацию можно найти в Readme.

Пример использования MapGL в стороннем проекте можно посмотреть в репозитории github.com/city-mobil/frontend_react-2gis, где реализована React-обертка. Также доступен npm пакет этой обертки - react-2gis.