Интеграция с React

Библиотека Deck.gl изначально проектировалась как компонентная система визуализации данных, хорошо сочетающаяся с архитектурой React. Основная идея заключается в том, что React управляет состоянием приложения и жизненным циклом компонентов, а Deck.gl отвечает за высокопроизводительный рендеринг слоёв на базе WebGL.

В React-приложении Deck.gl обычно используется через компонент DeckGL, который получает набор слоёв и параметры отображения через свойства (props).

Типичная схема взаимодействия выглядит следующим образом:

React State
      ↓
React Components
      ↓
DeckGL Component
      ↓
Layers
      ↓
WebGL Rendering

Такой подход позволяет использовать декларативную модель React при работе с большими объёмами геоданных.


Установка зависимостей

Для интеграции необходимы основные пакеты Deck.gl:

npm install deck.gl

Чаще всего дополнительно используются картографические библиотеки:

npm install react-map-gl maplibre-gl

или

npm install react-map-gl mapbox-gl

Современные проекты всё чаще используют MapLibre как полностью открытое решение без привязки к коммерческим лицензиям.


Базовый компонент DeckGL

Минимальная интеграция в React выглядит следующим образом:

import DeckGL from '@deck.gl/react';
import {ScatterplotLayer} from '@deck.gl/layers';

function App() {
  const layer = new ScatterplotLayer({
    id: 'points',
    data: [
      {
        position: [37.6176, 55.7558]
      }
    ],
    getPosition: d => d.position,
    getRadius: 1000,
    getFillColor: [255, 0, 0]
  });

  return (
    <DeckGL
      initialViewState={{
        longitude: 37.6176,
        latitude: 55.7558,
        zoom: 10
      }}
      controller={true}
      layers={[layer]}
    />
  );
}

export default App;

Здесь происходит:

  1. Создание слоя.
  2. Передача слоя в массив layers.
  3. Определение начального положения камеры.
  4. Включение пользовательского управления картой.

Структура React-компонента

На практике слои редко создаются непосредственно внутри JSX. Обычно используется разделение логики.

Пример:

import {useMemo} from 'react';
import DeckGL from '@deck.gl/react';
import {ScatterplotLayer} from '@deck.gl/layers';

function MapView({data}) {

  const layers = useMemo(() => [
    new ScatterplotLayer({
      id: 'cities',
      data,
      getPosition: d => d.coordinates,
      getRadius: 5000
    })
  ], [data]);

  return (
    <DeckGL
      controller
      layers={layers}
      initialViewState={{
        longitude: 30,
        latitude: 50,
        zoom: 4
      }}
    />
  );
}

Использование useMemo предотвращает пересоздание слоя при каждом рендере компонента.

Это особенно важно при работе с сотнями тысяч объектов.


Управление состоянием карты

React позволяет полностью контролировать состояние камеры через useState.

import {useState} from 'react';

const INITIAL_VIEW_STATE = {
  longitude: 37.6176,
  latitude: 55.7558,
  zoom: 10,
  pitch: 0,
  bearing: 0
};

function App() {

  const [viewState, setViewState] =
    useState(INITIAL_VIEW_STATE);

  return (
    <DeckGL
      viewState={viewState}
      controller
      onViewStateCha nge={({viewState}) =>
        setViewState(viewState)
      }
    />
  );
}

Преимущества такого подхода:

  • синхронизация карты с интерфейсом;
  • сохранение позиции между страницами;
  • поддержка URL-параметров;
  • интеграция с Redux и другими хранилищами состояния.

Интеграция с React Hooks

Deck.gl хорошо работает с современными возможностями React.

useState

Хранение данных:

const [points, setPoints] = useState([]);

Создание слоя:

const layers = [
  new ScatterplotLayer({
    id: 'points',
    data: points
  })
];

После обновления массива автоматически запускается новый рендер.


useEffect

Загрузка данных с сервера:

useEffect(() => {
  fetch('/api/points')
    .then(response => response.json())
    .then(data => setPoints(data));
}, []);

После получения данных слой обновится автоматически.


useMemo

Для тяжёлых вычислений:

const processedData = useMemo(() => {
  return rawData.map(item => ({
    ...item,
    value: item.sales * item.rate
  }));
}, [rawData]);

useCallback

Для обработчиков событий:

const handleClick = useCallback(info => {
  console.log(info.object);
}, []);

Использование useCallback уменьшает количество лишних пересозданий функций.


Интеграция с MapLibre

Одним из наиболее распространённых сценариев является наложение слоёв Deck.gl поверх интерактивной карты.

import DeckGL from '@deck.gl/react';
import Map from 'react-map-gl/maplibre';

function App() {
  return (
    <DeckGL
      controller
      layers={layers}
      initialViewState={{
        longitude: 37.6176,
        latitude: 55.7558,
        zoom: 10
      }}
    >
      <Map
        mapStyle="https://demotiles.maplibre.org/style.json"
      />
    </DeckGL>
  );
}

В этом случае:

  • MapLibre отображает подложку карты;
  • Deck.gl рисует слои поверх неё;
  • обе системы используют общую камеру.

Использование нескольких слоёв

React позволяет удобно собирать массив слоёв.

const layers = [
  roadsLayer,
  buildingsLayer,
  labelsLayer,
  heatmapLayer
];

Либо:

const layers = useMemo(() => {
  return [
    createRoadLayer(),
    createBuildingLayer(),
    createHeatmapLayer()
  ];
}, [data]);

Подобный подход облегчает поддержку крупных проектов.


Условный рендеринг слоёв

Поскольку слой является обычным объектом JavaScript, можно использовать стандартные механизмы React.

const layers = [
  showCities && cityLayer,
  showRoads && roadLayer,
  showHeatmap && heatmapLayer
].filter(Boolean);

Управление отображением производится через состояние:

const [showHeatmap, setShowHeatmap] =
  useState(true);

Обработка событий

Deck.gl поддерживает множество событий взаимодействия.

Обработка клика

const layer = new ScatterplotLayer({
  id: 'points',
  data,
  pickable: true,

  onClick: info => {
    console.log(info.object);
  }
});

Hover

const layer = new ScatterplotLayer({
  id: 'points',
  data,
  pickable: true,

  onHover: info => {
    console.log(info.x, info.y);
  }
});

Drag

const layer = new ScatterplotLayer({
  id: 'points',
  data,

  onDragStart: info => {},
  onDrag: info => {},
  onDragEnd: info => {}
});

Работа со всплывающими окнами

Типичный React-подход заключается в хранении выбранного объекта в состоянии.

const [selected, setSelected] =
  useState(null);

При клике:

onClick: info => {
  setSelected(info.object);
}

Отображение:

{
  selected && (
    <div className="popup">
      {selected.name}
    </div>
  )
}

Deck.gl отвечает за выбор объекта, а React — за пользовательский интерфейс.


Асинхронная загрузка данных

Загрузка может выполняться из различных источников.

Пример с REST API:

useEffect(() => {

  async function loadData() {

    const response =
      await fetch('/api/geojson');

    const json =
      await response.json();

    setData(json.features);
  }

  loadData();

}, []);

После обновления состояния слой автоматически перестроится.


Работа с GeoJSON

GeoJSON является наиболее распространённым форматом пространственных данных.

import {GeoJsonLayer}
  from '@deck.gl/layers';

const geoLayer = new GeoJsonLayer({
  id: 'geojson',
  data: geojsonData,
  filled: true,
  stroked: true,
  getFillColor: [50, 120, 255, 100]
});

В React компоненте:

const layers = useMemo(
  () => [geoLayer],
  [geojsonData]
);

Использование Context API

При большом количестве компонентов данные карты удобно хранить через Context.

import {createContext} from 'react';

export const MapContext =
  createContext(null);

Провайдер:

<MapContext.Provider
  value={{
    viewState,
    setViewState
  }}
>
  {children}
</MapContext.Provider>

Получение данных:

const {
  viewState,
  setViewState
} = useContext(MapContext);

Это позволяет синхронизировать состояние между панелями, фильтрами и картой.


Интеграция с Redux

В крупных приложениях состояние карты часто хранится централизованно.

const viewState =
  useSelector(state => state.map);

const dispatch = useDispatch();

Обновление камеры:

onViewStateCha nge={({viewState}) =>
  dispatch(updateViewState(viewState))
}

Преимущества:

  • единое хранилище;
  • предсказуемое состояние;
  • удобная отладка;
  • возможность воспроизведения действий пользователя.

Оптимизация производительности

Мемоизация слоёв

Наиболее важная оптимизация:

const layers = useMemo(
  () => [
    new ScatterplotLayer({...})
  ],
  [data]
);

Без неё каждый рендер React создаёт новый экземпляр слоя.


Стабильные обработчики

const onCl ick =
  useCallback(info => {
    setSelected(info.object);
  }, []);

Минимизация состояния

Нежелательно хранить в состоянии огромные массивы, если данные не меняются.

Плохой вариант:

setData([...data]);

при каждом взаимодействии пользователя.

Лучше обновлять только действительно изменившиеся данные.


Разделение компонентов

Вместо:

<App />

с тысячами строк логики предпочтительнее:

<Map />
<Sidebar />
<Filters />
<Toolbar />

Изолированные компоненты вызывают меньше лишних рендеров.


Серверный рендеринг

Deck.gl использует WebGL и не может выполняться на сервере в полном объёме.

Для проектов на Next.js обычно применяется динамический импорт.

import dynamic from 'next/dynamic';

const Map = dynamic(
  () => import('./Map'),
  {
    ssr: false
  }
);

Так компонент будет загружен только в браузере.


Организация структуры проекта

Пример структуры крупного React-приложения:

src/
│
├── components/
│   ├── Map/
│   │   ├── MapView.jsx
│   │   ├── Layers.jsx
│   │   └── Controls.jsx
│   │
│   ├── Sidebar/
│   └── Charts/
│
├── hooks/
│   ├── useMapData.js
│   ├── useViewport.js
│
├── store/
│
├── services/
│
└── pages/

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


Паттерн пользовательских хуков

Часто логика работы с картой выносится в отдельный хук.

function useCities() {

  const [cities, setCities] =
    useState([]);

  useEffect(() => {
    fetch('/api/cities')
      .then(r => r.json())
      .then(setCities);
  }, []);

  return cities;
}

Использование:

const cities = useCities();

Затем данные передаются непосредственно в слой.


Типичный поток данных

В большинстве React-приложений с Deck.gl используется следующий цикл:

API
 ↓
React State
 ↓
Hooks
 ↓
Layers
 ↓
DeckGL
 ↓
WebGL
 ↓
Пользовательское взаимодействие
 ↓
Обновление State
 ↓
Повторный рендер

Такая архитектура сочетает декларативную модель React с высокопроизводительным GPU-рендерингом Deck.gl, позволяя строить масштабируемые картографические системы, аналитические панели, геоинформационные сервисы и приложения визуализации больших пространственных данных.