Жизненный цикл компонентов в Deck.gl определяет порядок создания, обновления, рендеринга и уничтожения визуальных слоёв. Понимание этого процесса позволяет эффективно управлять данными, оптимизировать производительность и создавать собственные слои с предсказуемым поведением.
В основе библиотеки лежит концепция неизменяемых (immutable) свойств. Вместо изменения внутреннего состояния слоя напрямую создаются новые экземпляры слоёв с обновлёнными параметрами. Deck.gl самостоятельно определяет, какие данные изменились и какие операции необходимо выполнить повторно.
Типичный жизненный цикл слоя включает следующие этапы:
Каждый слой создаётся как объект определённого класса.
import {ScatterplotLayer} from '@deck.gl/layers';
const layer = new ScatterplotLayer({
id: 'cities',
data: cities,
getPosition: d => d.coordinates,
getRadius: 1000
});
На этом этапе происходит:
props);Само создание объекта ещё не приводит к немедленной загрузке данных в GPU. Большинство тяжёлых операций выполняется позже в процессе инициализации.
После добавления слоя в объект Deck запускается метод
initializeState().
initializeState() {
this.state = {
selectedObject: null
};
}
Основные задачи этапа:
Для пользовательских слоёв метод обычно переопределяется.
Пример:
initializeState() {
const attributeManager = this.getAttributeManager();
attributeManager.add({
positions: {
size: 3,
accessor: 'getPosition'
}
});
}
В данном случае создаётся атрибут вершин, который впоследствии будет автоматически обновляться при изменении данных.
Каждый слой содержит внутренний объект состояния.
this.state
В отличие от props, состояние предназначено для хранения
служебной информации:
initializeState() {
this.state = {
texture: null,
loading: false,
hoveredIndex: -1
};
}
Обычно в состоянии хранятся:
Важно понимать различие:
| Props | State |
|---|---|
| Передаются извне | Управляются слоем |
| Неизменяемы | Могут изменяться |
| Определяют конфигурацию | Хранят внутренние данные |
Одной из важнейших особенностей Deck.gl является механизм определения изменений.
При создании нового экземпляра слоя библиотека сравнивает:
oldProps
newProps
Например:
new ScatterplotLayer({
id: 'cities',
data: newCities
});
Deck.gl проверяет:
Результат сравнения помещается в объект changeFlags.
changeFlags содержит информацию обо всех обнаруженных
изменениях.
Пример структуры:
{
dataChanged: true,
propsChanged: true,
viewportChanged: false,
stateChanged: false
}
Основные флаги:
Указывает на изменение источника данных.
{
dataChanged: true
}
Обычно приводит к пересчёту атрибутов.
Срабатывает при изменении свойств слоя.
{
propsChanged: true
}
Например:
getRadius: d => d.population
заменяется на
getRadius: d => d.size
Возникает при изменении камеры.
{
viewportChanged: true
}
Причины:
Отражает изменения внутреннего состояния.
{
stateChanged: true
}
После вычисления изменений вызывается:
shouldUpdateState({changeFlags})
По умолчанию:
shouldUpdateState({changeFlags}) {
return changeFlags.somethingChanged;
}
Метод позволяет самостоятельно определить необходимость обновления.
Пример:
shouldUpdateState({changeFlags}) {
return (
changeFlags.dataChanged ||
changeFlags.propsChanged
);
}
Это полезно для сокращения количества дорогостоящих вычислений.
Если обновление требуется, вызывается метод:
updateState(params)
Он является центральной точкой жизненного цикла.
Пример:
updateState({
props,
oldProps,
changeFlags
}) {
if (changeFlags.dataChanged) {
this.recalculateBuffers();
}
}
Типичные задачи:
Практически каждый слой использует объект:
this.getAttributeManager()
Он отвечает за управление атрибутами вершин.
Пример регистрации:
attributeManager.add({
positions: {
size: 3,
accessor: 'getPosition'
},
colors: {
size: 4,
accessor: 'getColor'
}
});
При изменении данных менеджер автоматически определяет необходимость обновления.
Полный пересчёт атрибутов может быть дорогой операцией.
Deck.gl позволяет обновлять только необходимые части данных.
attributeManager.invalidate('positions');
Обновится только атрибут координат.
Пример:
updateState({changeFlags}) {
if (changeFlags.dataChanged) {
this.getAttributeManager().invalidate('positions');
}
}
Такой подход особенно эффективен для наборов данных из сотен тысяч объектов.
Для более точного контроля используется механизм триггеров обновления.
new ScatterplotLayer({
data,
getRadius: d => d.population,
updateTriggers: {
getRadius: scale
}
});
Если значение scale изменится, Deck.gl автоматически
пересчитает радиусы.
updateTriggers: {
getColor: selectedCategory
}
Без этого механизма библиотека могла бы не заметить изменение внешних зависимостей внутри функции доступа.
После завершения обновления начинается этап рендеринга.
Deck.gl вызывает:
draw()
Большинство встроенных слоёв реализуют этот метод самостоятельно.
В пользовательском слое возможно переопределение:
draw({uniforms}) {
const model = this.state.model;
model.draw({
uniforms
});
}
На этом этапе выполняются:
Многие слои реагируют не только на данные, но и на состояние карты.
Пример:
changeFlags.viewportChanged
Срабатывает при:
viewState.longitude
viewState.latitude
viewState.zoom
viewState.pitch
viewState.bearing
изменении.
Это позволяет пересчитывать элементы, зависящие от масштаба или перспективы.
Композитные слои наследуются от класса:
CompositeLayer
Они не рисуют объекты напрямую.
Вместо этого создают дочерние слои:
renderLayers() {
return [
new ScatterplotLayer({...}),
new TextLayer({...})
];
}
Каждый дочерний слой имеет собственный жизненный цикл:
Родительский слой управляет их созданием и синхронизацией.
Deck.gl поддерживает асинхронные источники данных.
new ScatterplotLayer({
data: fetch('/data.json')
});
Пока данные загружаются, слой остаётся активным.
После завершения загрузки происходит:
dataChanged = true
Затем автоматически запускаются:
Без необходимости ручного вмешательства.
Во время работы слоя могут создаваться:
Пример:
initializeState() {
this.state.model = this._createModel();
}
Эти ресурсы должны корректно освобождаться.
Перед удалением слоя вызывается:
finalizeState()
Это финальная стадия жизненного цикла.
Пример:
finalizeState() {
this.state.texture?.delete();
this.state.model?.delete();
}
Задачи метода:
Если не выполнять очистку, возможно накопление утечек памяти.
Для нового слоя последовательность выглядит следующим образом:
constructor
↓
initializeState
↓
updateState
↓
draw
При изменении данных:
shouldUpdateState
↓
updateState
↓
draw
При удалении:
finalizeState
Полный цикл:
Создание
↓
initializeState
↓
updateState
↓
draw
↓
updateState
↓
draw
↓
updateState
↓
draw
↓
finalizeState
При использовании Deck.gl совместно с React жизненный цикл слоя не совпадает с жизненным циклом React-компонента.
Пример:
<DeckGL layers={[layer]} />
При каждом рендере React может создаваться новый экземпляр слоя:
const layer = new ScatterplotLayer({...});
Однако Deck.gl сравнивает новый экземпляр со старым по идентификатору:
id: 'cities'
Если идентификатор совпадает, происходит обновление существующего внутреннего состояния, а не полное пересоздание GPU-ресурсов.
Это позволяет эффективно использовать декларативный подход React без существенных потерь производительности.
Тяжёлые вычисления следует выполнять только при необходимости.
if (changeFlags.dataChanged) {
this.buildSpatialIndex();
}
updateTriggers: {
getColor: theme
}
Это снижает количество лишних пересчётов.
finalizeState() {
this.state.model?.delete();
}
Любой объект GPU должен иметь соответствующую очистку.
attributeManager.invalidate('colors');
Локальное обновление значительно дешевле полного пересчёта всех атрибутов.
Нежелательно:
this.state = {
data: props.data
};
Предпочтительно:
this.props.data
Хранение дубликатов данных усложняет отслеживание изменений и увеличивает потребление памяти.
Создание слоя
↓
initializeState()
↓
Сравнение props
↓
changeFlags
↓
shouldUpdateState()
↓
updateState()
↓
Обновление атрибутов
↓
draw()
↓
Рендеринг GPU
↓
Изменение данных
↓
updateState()
↓
draw()
↓
Удаление слоя
↓
finalizeState()
Понимание этой последовательности является основой разработки производительных визуализаций в Deck.gl, поскольку все механизмы обновления данных, управления памятью и взаимодействия с GPU строятся вокруг описанного жизненного цикла слоя.