Интеграция CesiumJS в React требует учёта фундаментального различия между моделью декларативного UI и императивным рендерингом WebGL-сцены. React управляет виртуальным DOM и стремится пересоздавать интерфейс на основе состояния, тогда как CesiumJS поддерживает долгоживущую графическую сцену, где объекты изменяются напрямую через API Viewer, Scene, Entities и Primitive.
Наиболее распространённый подход заключается в создании контейнера
DOM-элемента и инициализации экземпляра Cesium.Viewer
внутри useEffect. Ключевая идея — изолировать Cesium от
механизма повторного рендера React.
import { useEffect, useRef } from "react";
import * as Cesium from "cesium";
import "cesium/Build/Cesium/Widgets/widgets.css";
export default function CesiumMap() {
const containerRef = useRef(null);
const viewerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
viewerRef.current = new Cesium.Viewer(containerRef.current, {
animation: false,
timeline: false,
terrainProvider: Cesium.createWorldTerrain(),
});
return () => {
if (viewerRef.current && !viewerRef.current.isDestroyed()) {
viewerRef.current.destroy();
viewerRef.current = null;
}
};
}, []);
return <div ref={containerRef} style={{ width: "100%", height: "100vh" }} />;
}
Ключевое правило архитектуры — экземпляр Viewer должен
существовать независимо от жизненного цикла React-компонента, за
исключением инициализации и уничтожения.
Cesium Viewer является тяжёлым объектом, включающим WebGL-контекст, сцену, камеры, источники данных и слой UI. Повторная инициализация приводит к утечкам памяти и деградации производительности.
Стабильная схема управления включает:
useRefdestroy() при размонтированииДополнительно важно учитывать StrictMode в React 18, который может вызывать двойной монтаж в dev-режиме. Это требует защиты от повторной инициализации:
const initializedRef = useRef(false);
useEffect(() => {
if (initializedRef.current) return;
initializedRef.current = true;
viewerRef.current = new Cesium.Viewer(containerRef.current);
}, []);
Cesium не должен зависеть от частых обновлений React state. Любое изменение state, приводящее к перерисовке сцены, становится узким местом.
Правильный подход — использовать React только как слой управления конфигурацией, а Cesium как независимый runtime.
Пример: управление объектами через эффекты без пересоздания Viewer.
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
viewer.entities.add({
id: "point-1",
position: Cesium.Cartesian3.fromDegrees(71.4304, 51.1282),
point: {
pixelSize: 10,
color: Cesium.Color.RED,
},
});
}, []);
Обновление данных выполняется через imperative API, а не через JSX.
React может перерендеривать компонент десятки раз при изменении состояния UI. Cesium при этом не должен реагировать на каждый render.
Для защиты используются:
useRef для хранения Cesium объектовuseMemo для статических
конфигурацийПример разделения:
function CesiumContainer({ config }) {
const containerRef = useRef();
useEffect(() => {
const viewer = new Cesium.Viewer(containerRef.current, config);
return () => viewer.destroy();
}, []);
return <div ref={containerRef} />;
}
UI-компонент отдельно управляет config, но не
пересоздаёт Viewer при каждом изменении.
Cesium Entities можно обновлять без пересоздания всей сцены. Это позволяет частично интегрировать реактивность.
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
const entity = viewer.entities.getById("point-1");
if (entity) {
entity.position = Cesium.Cartesian3.fromDegrees(lng, lat);
}
}, [lng, lat]);
Этот подход ограничивает нагрузку на WebGL и избегает полной перерисовки сцены.
Для динамических сценариев используется CZML — формат описания временных данных Cesium.
В React интеграции CZML загружается один раз, а затем обновляется через DataSource.
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
const czmlDataSource = new Cesium.CzmlDataSource();
viewer.dataSources.add(czmlDataSource);
czmlDataSource.load("/data/flight.czml");
return () => {
viewer.dataSources.remove(czmlDataSource);
};
}, []);
Это особенно эффективно при визуализации траекторий, симуляций и трекинга объектов.
Существует экосистема обёрток над CesiumJS, где наиболее известной является Resium. Она предоставляет декларативные React-компоненты для Cesium.
Пример использования:
import { Viewer, Entity } from "resium";
import { Cartesian3 } from "cesium";
export default function Map() {
return (
<Viewer full>
<Entity
position={Cartesian3.fromDegrees(71.4304, 51.1282)}
point={{ pixelSize: 10, color: Cesium.Color.YELLOW }}
/>
</Viewer>
);
}
Подход Resium удобен для декларативной разработки, но имеет ограничения:
Поэтому в высоконагруженных проектах предпочтительнее прямой доступ к CesiumJS.
Камера Cesium — отдельный объект сцены, который должен контролироваться через imperative API.
function flyToMoscow(viewer) {
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(37.6173, 55.7558, 10000),
});
}
В React это обычно оборачивается в useCallback или
сервисный слой.
const flyTo = useCallback(() => {
viewerRef.current?.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lng, lat, height),
});
}, [lng, lat, height]);
Terrain является одним из самых ресурсоёмких компонентов Cesium. При React-интеграции важно избегать повторного создания terrain provider.
const terrainProvider = useMemo(() => {
return Cesium.createWorldTerrain();
}, []);
Это предотвращает лишние сетевые запросы и пересоздание слоя высот.
В крупных приложениях Cesium обычно отделяется в отдельный слой:
Пример сервисного слоя:
class CesiumService {
constructor(container) {
this.viewer = new Cesium.Viewer(container);
}
addEntity(entity) {
return this.viewer.entities.add(entity);
}
destroy() {
this.viewer.destroy();
}
}
React-компонент становится тонким адаптером:
useEffect(() => {
const service = new CesiumService(containerRef.current);
return () => service.destroy();
}, []);
Cesium активно использует WebGL ресурсы, поэтому неправильное управление жизненным циклом приводит к утечкам:
Рекомендуется:
destroy()viewer.entities.removeAll()viewer.dataSources.remove()Для синхронизации состояния часто используется промежуточный слой событий:
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
handler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position);
if (picked) {
console.log("Selected object", picked);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
return () => handler.destroy();
}, []);
Это позволяет отделить UI-логику React от интерактивной модели Cesium.
При интеграции с React важно учитывать потоковую природу Cesium 3D Tiles:
useEffect(() => {
const viewer = viewerRef.current;
if (!viewer) return;
const tileset = new Cesium.Cesium3DTileset({
url: "/tileset.json",
});
viewer.scene.primitives.add(tileset);
return () => {
viewer.scene.primitives.remove(tileset);
};
}, []);
Тайлы загружаются асинхронно и не должны пересоздаваться при каждом обновлении UI.
React выполняет задачи:
Cesium выполняет задачи:
Слабая связность между слоями обеспечивает предсказуемость поведения и стабильную производительность при масштабировании сцены до миллионов объектов.