Типизация TypeScript

TypeScript в экосистеме Kepler.gl используется не как вспомогательный слой, а как структурный каркас, обеспечивающий согласованность данных между визуализацией, состоянием приложения и графическим рендерингом на базе deck.gl. Типизация охватывает слои Redux-архитектуры, конфигурации слоёв карты, описание датасетов и взаимодействие с геоданными.

Основная сложность заключается в том, что Kepler.gl объединяет несколько доменов: геопространственные данные, декларативные визуализации и состояние интерфейса. Без строгих типов эти области быстро начинают конфликтовать на уровне контрактов.


Базовые принципы типизации

Типизация в Kepler.gl строится вокруг нескольких фундаментальных идей:

  • унификация структуры геоданных через GeoJSON-подобные модели
  • строгие интерфейсы Redux state
  • декларативные типы слоёв и визуализаций
  • изоляция конфигураций через typed config objects
  • минимизация any в критических слоях

Ключевой целью является предотвращение ошибок на этапе компиляции при трансформации данных между слоями карты и визуализацией.


Типизация датасетов

Основная единица данных — датасет, который описывает таблицу с географическими координатами и атрибутами.

Типовая модель:

export type KeplerTableRow = {
  [key: string]: string | number | boolean | null;
};

export interface KeplerDataset {
  id: string;
  label: string;
  color?: number[];
  dat a: KeplerTableRow[];
  fields: KeplerField[];
}

Описание полей:

export interface KeplerField {
  name: string;
  type: 'string' | 'integer' | 'real' | 'timestamp' | 'boolean';
  format?: string;
}

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


Геопространственные типы

Геоданные в Kepler.gl опираются на расширенные GeoJSON-конструкции. Типы обычно дополняются координатными контрактами.

export type LonLat = [number, number];

export interface GeoPoint {
  type: 'Point';
  coordinates: LonLat;
}

export interface GeoLine {
  type: 'LineString';
  coordinates: LonLat[];
}

export interface GeoPolygon {
  type: 'Polygon';
  coordinates: LonLat[][];
}

Для унификации:

export type GeoFeature = GeoPoint | GeoLine | GeoPolygon;

В реальных сценариях типы часто расширяются дополнительными свойствами через generics:

export interface GeoFeatureWithProps<T = Record<string, unknown>> {
  type: string;
  geometry: GeoFeature;
  properties: T;
}

Типизация состояния Redux

Архитектура Kepler.gl построена вокруг глобального состояния, содержащего карты, датасеты, UI-состояния и конфигурации слоёв.

export interface KeplerGlState {
  visState: VisState;
  mapState: MapState;
  uiState: UiState;
}

VisState

Отвечает за визуализацию:

export interface VisState {
  datasets: Record<string, KeplerDataset>;
  layers: LayerState[];
  filters: FilterState[];
  interactionConfig: InteractionConfig;
}

MapState

export interface MapState {
  latitude: number;
  longitude: number;
  zoom: number;
  bearing: number;
  pitch: number;
}

UiState

export interface UiState {
  activeSidePanel: string | null;
  currentModal: string | null;
  readOnly: boolean;
}

Типизация слоёв визуализации

Слой — ключевая абстракция, связывающая данные и визуальное представление. Каждый слой имеет собственную конфигурацию.

export interface BaseLayerConfig {
  id: string;
  type: string;
  dataId: string;
  label: string;
  isVisible: boolean;
}

Расширенные слои:

export interface PointLayerConfig extends BaseLayerConfig {
  type: 'point';
  radius: number;
  color: number[];
}
export interface LineLayerConfig extends BaseLayerConfig {
  type: 'line';
  thickness: number;
  color: number[];
}
export type LayerState = PointLayerConfig | LineLayerConfig;

Типизация слоя напрямую влияет на генерацию deck.gl-подслоёв, где каждая конфигурация транслируется в WebGL-рендеринг.


Типизация фильтров

Фильтрация данных требует строгого описания диапазонов и типов полей.

export interface BaseFilter {
  id: string;
  dataId: string;
  name: string;
}
export interface RangeFilter extends BaseFilter {
  type: 'range';
  value: [number, number];
}
export interface TimeFilter extends BaseFilter {
  type: 'timeRange';
  value: [number, number];
}
export type FilterState = RangeFilter | TimeFilter;

Типизация фильтров критична для предотвращения неконсистентных состояний UI и данных.


Типизация взаимодействий

Интерактивность карты описывается через конфигурационные объекты:

export interface InteractionConfig {
  tooltip: boolean;
  brush: boolean;
  geocoder: boolean;
}

Расширение через строгие булевые флаги позволяет контролировать поведение UI без побочных эффектов.


Action-типизация Redux

Redux actions в Kepler.gl типизируются через discriminated unions.

export interface AddDatasetAction {
  type: 'ADD_DATASET';
  payload: KeplerDataset;
}
export interface UpdateMapStateAction {
  type: 'UPDATE_MAP_STATE';
  payload: Partial<MapState>;
}
export type KeplerAction =
  | AddDatasetAction
  | UpdateMapStateAction;

Такой подход позволяет reducer-ам безопасно обрабатывать действия без runtime-проверок.


Типизация reducer-ов

export function visStateReducer(
  state: VisState,
  action: KeplerAction
): VisState {
  switch (action.type) {
    case 'ADD_DATASET':
      return {
        ...state,
        datasets: {
          ...state.datasets,
          [action.payload.id]: action.payload
        }
      };

    default:
      return state;
  }
}

Типизация здесь предотвращает несоответствие структуры состояния при обновлениях.


Generics в конфигурациях

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

export interface TypedDataset<T = KeplerTableRow> {
  id: string;
  dat a: T[];
}

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


Модульное расширение типов

Kepler.gl часто интегрируется в сторонние приложения, где требуется расширение базовых интерфейсов через declaration merging.

declare module 'kepler.gl' {
  interface KeplerGlState {
    customPluginState?: unknown;
  }
}

Такой механизм позволяет внедрять плагины без модификации ядра библиотеки.


Типизация конфигураций визуализации

Конфигурации слоёв и визуальных параметров часто сериализуются, поэтому требуют стабильных контрактов:

export interface LayerVisualConfig {
  opacity: number;
  strokeColor: [number, number, number];
  fillColor: [number, number, number];
}

Эти типы используются при сохранении и восстановлении проектов карт.


Связь TypeScript и deck.gl типов

deck.gl предоставляет собственную систему типов для слоёв WebGL, которые в Kepler.gl выступают нижним уровнем абстракции.

Типичный мост:

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

export interface DeckLayerProps {
  id: string;
  dat a: any[];
  getPosition: (d: any) => [number, number];
}

Kepler.gl преобразует собственные LayerConfig в deck.gl props через адаптеры с типизированными контрактами.


Типизация Mapbox взаимодействия

Интеграция с картографическим движком Mapbox GL JS требует описания viewport-состояния и стилей:

export interface MapViewport {
  longitude: number;
  latitude: number;
  zoom: number;
  pitch: number;
  bearing: number;
}

Эти типы синхронизируются с Redux state и UI слоями.


Типизация событий

Событийная модель описывается через строгие интерфейсы:

export interface LayerClickEvent {
  layerId: string;
  object: unknown;
  coordinates: [number, number];
}
export type KeplerEvent = LayerClickEvent;

Это позволяет унифицировать обработчики интерактивности.


Стратегии избегания any

В крупных частях Kepler.gl any заменяется на:

  • unknown для небезопасных входов
  • generics для расширяемых структур
  • union types для переключаемых состояний
  • mapped types для трансформаций состояния

Пример mapped type:

type OptionalConfig<T> = {
  [K in keyof T]?: T[K];
};

Типизация сериализации состояния

При сохранении проектов требуется строгая структура:

export interface KeplerMapConfig {
  version: string;
  config: KeplerGlState;
}

Это гарантирует обратную совместимость между версиями состояния и визуализацией.