TypeScript предоставляет строгую систему типов, которая значительно упрощает разработку сложных геопространственных приложений на базе Deck.gl. Благодаря типизации повышается надежность кода, улучшается автодополнение в редакторах, упрощается рефакторинг и снижается количество ошибок при работе с большими наборами данных и слоями визуализации.
Deck.gl включает встроенные TypeScript-определения, поэтому дополнительная установка пакетов типов обычно не требуется.
Установка основных зависимостей:
npm install deck.gl
npm install typescript
Минимальный файл конфигурации TypeScript:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"moduleResolution": "Node",
"esModuleInterop": true,
"skipLibCheck": true
}
}
Особенно рекомендуется включать режим строгой типизации:
{
"strict": true
}
Этот режим помогает выявлять ошибки еще на этапе компиляции.
Одним из важнейших аспектов работы с Deck.gl является описание структуры отображаемых объектов.
Допустим, имеется набор городов:
interface City {
id: number;
name: string;
population: number;
coordinates: [number, number];
}
Массив данных:
const cities: City[] = [
{
id: 1,
name: "Almaty",
population: 2000000,
coordinates: [76.886, 43.238]
},
{
id: 2,
name: "Astana",
population: 1300000,
coordinates: [71.449, 51.169]
}
];
Теперь TypeScript гарантирует наличие всех необходимых свойств у каждого объекта.
Практически все слои Deck.gl поддерживают дженерики.
Пример создания ScatterplotLayer:
import {ScatterplotLayer} from '@deck.gl/layers';
interface City {
id: number;
name: string;
population: number;
coordinates: [number, number];
}
const layer = new ScatterplotLayer<City>({
id: 'cities',
data: cities,
getPosition: city => city.coordinates,
getRadius: city => city.population / 1000,
getFillColor: () => [255, 140, 0]
});
После указания типа <City> все callback-функции
автоматически получают корректный тип данных.
Например:
getPosition: city => city.coordinates
Переменная city будет иметь тип:
City
Поэтому редактор сможет подсказывать доступные свойства.
Каждый слой обладает набором параметров, описанных через интерфейсы свойств.
Пример:
import {ScatterplotLayerProps} from '@deck.gl/layers';
Создание конфигурации слоя:
const props: ScatterplotLayerProps<City> = {
id: 'cities',
data: cities,
getPosition: city => city.coordinates
};
Если будет передано недопустимое свойство:
const props: ScatterplotLayerProps<City> = {
id: 'cities',
invalidProperty: true
};
TypeScript немедленно сообщит об ошибке.
Deck.gl активно использует события взаимодействия.
Пример обработчика клика:
import {PickingInfo} from '@deck.gl/core';
const layer = new ScatterplotLayer<City>({
id: 'cities',
data: cities,
pickable: true,
onClick: (info: PickingInfo<City>) => {
console.log(info.object?.name);
}
});
Тип PickingInfo<T> позволяет получать строго
типизированный объект.
Доступные свойства:
info.object
info.coordinate
info.index
info.layer
info.viewport
info.x
info.y
Тип объекта определяется автоматически:
City | null
При выборе объекта необходимо учитывать отсутствие выбранного элемента.
Небезопасный вариант:
console.log(info.object.name);
Безопасный вариант:
console.log(info.object?.name);
Либо:
if (info.object) {
console.log(info.object.name);
}
Подобный подход особенно важен при включенном режиме
strictNullChecks.
Экземпляр Deck также имеет собственные типы.
Создание визуализации:
import {Deck} from '@deck.gl/core';
const deck = new Deck({
initialViewState: {
longitude: 76.886,
latitude: 43.238,
zoom: 10
},
controller: true,
layers: [layer]
});
Тип объекта:
Deck
После этого становятся доступны методы:
deck.setProps(...)
deck.pickObject(...)
deck.pickMultipleObjects(...)
deck.finalize()
Состояние камеры желательно описывать отдельным интерфейсом.
Пример:
import {MapViewState} from '@deck.gl/core';
const viewState: MapViewState = {
longitude: 76.886,
latitude: 43.238,
zoom: 11,
pitch: 30,
bearing: 0
};
Такой подход исключает ошибки в названиях параметров камеры.
Например:
const state: MapViewState = {
longitude: 76.886,
latitude: 43.238,
zomm: 10
};
Компилятор сообщит об опечатке в свойстве zoom.
Deck.gl часто используется совместно с GeoJSON.
Описание объекта:
interface CountryProperties {
name: string;
population: number;
}
Использование библиотеки GeoJSON:
import {Feature} from 'geojson';
type CountryFeature = Feature<
GeoJSON.Geometry,
CountryProperties
>;
Создание слоя:
import {GeoJsonLayer} from '@deck.gl/layers';
const layer = new GeoJsonLayer<CountryFeature>({
id: 'countries',
data: geojson,
getFillColor: feature => {
return feature.properties.population > 10000000
? [255, 0, 0]
: [0, 255, 0];
}
});
Теперь свойства GeoJSON доступны с полной типовой безопасностью.
Нередко данные содержат большое количество полей.
Пример:
interface Vehicle {
id: string;
model: string;
speed: number;
heading: number;
coordinates: [number, number];
online: boolean;
}
Использование:
new ScatterplotLayer<Vehicle>({
id: 'vehicles',
data: vehicles,
getPosition: vehicle => vehicle.coordinates,
getRadius: vehicle => vehicle.speed
});
Каждый аксессор автоматически получает тип Vehicle.
В крупных проектах рекомендуется выносить вычисления в отдельные функции.
Например:
function getVehiclePosition(
vehicle: Vehicle
): [number, number] {
return vehicle.coordinates;
}
Использование:
new ScatterplotLayer<Vehicle>({
getPosition: getVehiclePosition
});
Подход облегчает повторное использование логики и улучшает читаемость.
TypeScript позволяет строить сложные иерархии данных.
Базовый интерфейс:
interface MapObject {
id: string;
coordinates: [number, number];
}
Наследник:
interface Vehicle extends MapObject {
speed: number;
heading: number;
}
Еще один наследник:
interface Sensor extends MapObject {
temperature: number;
}
Теперь общие свойства не дублируются.
Некоторые наборы данных содержат объекты разных типов.
Пример:
interface Vehicle {
type: 'vehicle';
speed: number;
}
interface Sensor {
type: 'sensor';
temperature: number;
}
type MapEntity = Vehicle | Sensor;
Проверка типа:
function process(entity: MapEntity) {
if (entity.type === 'vehicle') {
console.log(entity.speed);
} else {
console.log(entity.temperature);
}
}
Такой механизм называется дискриминирующим объединением.
Deck.gl позволяет создавать собственные классы слоев.
Описание данных:
interface PointData {
id: string;
position: [number, number];
}
Создание класса:
import {Layer} from '@deck.gl/core';
export class CustomLayer extends Layer<PointData> {
}
Теперь весь слой работает с объектами типа
PointData.
Кастомный слой может хранить внутреннее состояние.
Описание:
interface LayerState {
hoveredIndex: number;
}
Использование:
export class CustomLayer extends Layer<
PointData,
LayerState
> {
}
Доступ к состоянию:
this.state.hoveredIndex
Тип будет определен автоматически.
TypeScript предоставляет встроенные служебные типы.
Делает свойства необязательными:
type PartialCity = Partial<City>;
Результат:
{
id?: number;
name?: string;
population?: number;
coordinates?: [number, number];
}
Выбор нескольких полей:
type CitySummary = Pick<
City,
'id' | 'name'
>;
Полученный тип:
{
id: number;
name: string;
}
Исключение полей:
type CityWithoutPopulation =
Omit<City, 'population'>;
Создание словаря:
const cityMap: Record<number, City> = {
1: cities[0],
2: cities[1]
};
Получение данных с сервера:
async function loadCities(): Promise<City[]> {
const response = await fetch('/api/cities');
return response.json();
}
Использование:
const cities = await loadCities();
Тип результата:
City[]
Создание универсального генератора слоев:
function createLayer<T>(
id: string,
data: T[],
getPosition: (item: T) => [number, number]
) {
return new ScatterplotLayer<T>({
id,
data,
getPosition
});
}
Использование:
const layer = createLayer(
'cities',
cities,
city => city.coordinates
);
Тип City будет выведен автоматически.
Пример функционального компонента:
import DeckGL from '@deck.gl/react';
interface Props {
cities: City[];
}
Компонент:
export function Map({cities}: Props) {
const layer = new ScatterplotLayer<City>({
id: 'cities',
data: cities,
getPosition: city => city.coordinates
});
return (
<DeckGL
controller={true}
layers={[layer]}
/>
);
}
Все свойства компонента и слоя остаются строго типизированными.
Для крупных проектов рекомендуется выделять отдельный каталог:
src/
├─ types/
│ ├─ city.ts
│ ├─ vehicle.ts
│ ├─ sensor.ts
│ └─ geojson.ts
├─ layers/
├─ services/
└─ components/
Пример файла типов:
export interface City {
id: number;
name: string;
population: number;
coordinates: [number, number];
}
Импорт:
import {City} from '../types/city';
Централизация типов значительно упрощает сопровождение больших приложений.
Нежелательный вариант:
new ScatterplotLayer({
data: cities
});
Правильный вариант:
new ScatterplotLayer<City>({
data: cities
});
Нежелательно:
function process(data: any) {
}
Предпочтительно:
function process(data: City) {
}
Ошибка:
console.log(info.object.name);
Безопасный вариант:
console.log(info.object?.name);
Нежелательно:
{
"strict": false
}
Предпочтительно:
{
"strict": true
}
Строгая типизация особенно полезна в проектах с большим количеством слоев, сложными структурами данных, взаимодействием с GeoJSON, обработкой пользовательских событий и разработкой собственных расширений Deck.gl. Грамотное использование интерфейсов, дженериков, объединений типов и встроенных средств TypeScript делает код визуализации более надежным, масштабируемым и удобным для сопровождения.