Жизненный цикл компонентов

Жизненный цикл компонентов в Deck.gl определяет порядок создания, обновления, рендеринга и уничтожения визуальных слоёв. Понимание этого процесса позволяет эффективно управлять данными, оптимизировать производительность и создавать собственные слои с предсказуемым поведением.

В основе библиотеки лежит концепция неизменяемых (immutable) свойств. Вместо изменения внутреннего состояния слоя напрямую создаются новые экземпляры слоёв с обновлёнными параметрами. Deck.gl самостоятельно определяет, какие данные изменились и какие операции необходимо выполнить повторно.

Типичный жизненный цикл слоя включает следующие этапы:

  1. Создание экземпляра слоя.
  2. Инициализация ресурсов.
  3. Сравнение новых и старых свойств.
  4. Обновление атрибутов и состояния.
  5. Рендеринг.
  6. Очистка ресурсов при удалении.

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

Каждый слой создаётся как объект определённого класса.

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

const layer = new ScatterplotLayer({
  id: 'cities',
  data: cities,
  getPosition: d => d.coordinates,
  getRadius: 1000
});

На этом этапе происходит:

  • создание экземпляра класса;
  • сохранение входных свойств (props);
  • подготовка внутренних структур данных;
  • регистрация слоя в менеджере слоёв Deck.gl.

Само создание объекта ещё не приводит к немедленной загрузке данных в GPU. Большинство тяжёлых операций выполняется позже в процессе инициализации.


Инициализация слоя

После добавления слоя в объект Deck запускается метод initializeState().

initializeState() {
  this.state = {
    selectedObject: null
  };
}

Основные задачи этапа:

  • создание внутреннего состояния;
  • регистрация атрибутов;
  • подготовка буферов GPU;
  • настройка менеджеров атрибутов.

Для пользовательских слоёв метод обычно переопределяется.

Пример:

initializeState() {
  const attributeManager = this.getAttributeManager();

  attributeManager.add({
    positions: {
      size: 3,
      accessor: 'getPosition'
    }
  });
}

В данном случае создаётся атрибут вершин, который впоследствии будет автоматически обновляться при изменении данных.


Объект state

Каждый слой содержит внутренний объект состояния.

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

changeFlags содержит информацию обо всех обнаруженных изменениях.

Пример структуры:

{
  dataChanged: true,
  propsChanged: true,
  viewportChanged: false,
  stateChanged: false
}

Основные флаги:

dataChanged

Указывает на изменение источника данных.

{
  dataChanged: true
}

Обычно приводит к пересчёту атрибутов.


propsChanged

Срабатывает при изменении свойств слоя.

{
  propsChanged: true
}

Например:

getRadius: d => d.population

заменяется на

getRadius: d => d.size

viewportChanged

Возникает при изменении камеры.

{
  viewportChanged: true
}

Причины:

  • масштабирование;
  • вращение;
  • панорамирование карты.

stateChanged

Отражает изменения внутреннего состояния.

{
  stateChanged: true
}

Метод shouldUpdateState()

После вычисления изменений вызывается:

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();
  }
}

Типичные задачи:

  • пересчёт атрибутов;
  • загрузка текстур;
  • обновление буферов;
  • кэширование вычислений.

Работа с AttributeManager

Практически каждый слой использует объект:

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');
  }
}

Такой подход особенно эффективен для наборов данных из сотен тысяч объектов.


Параметр updateTriggers

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

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
  });
}

На этом этапе выполняются:

  • передача uniform-переменных;
  • запуск шейдеров;
  • отправка команд GPU;
  • отображение результата на экране.

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

Многие слои реагируют не только на данные, но и на состояние карты.

Пример:

changeFlags.viewportChanged

Срабатывает при:

viewState.longitude
viewState.latitude
viewState.zoom
viewState.pitch
viewState.bearing

изменении.

Это позволяет пересчитывать элементы, зависящие от масштаба или перспективы.


Жизненный цикл композитных слоёв

Композитные слои наследуются от класса:

CompositeLayer

Они не рисуют объекты напрямую.

Вместо этого создают дочерние слои:

renderLayers() {
  return [
    new ScatterplotLayer({...}),
    new TextLayer({...})
  ];
}

Каждый дочерний слой имеет собственный жизненный цикл:

  1. Инициализация.
  2. Обновление.
  3. Рендеринг.
  4. Очистка.

Родительский слой управляет их созданием и синхронизацией.


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

Deck.gl поддерживает асинхронные источники данных.

new ScatterplotLayer({
  data: fetch('/data.json')
});

Пока данные загружаются, слой остаётся активным.

После завершения загрузки происходит:

dataChanged = true

Затем автоматически запускаются:

  1. updateState()
  2. обновление атрибутов
  3. draw()

Без необходимости ручного вмешательства.


Управление ресурсами GPU

Во время работы слоя могут создаваться:

  • текстуры;
  • буферы;
  • модели;
  • framebuffer-объекты.

Пример:

initializeState() {
  this.state.model = this._createModel();
}

Эти ресурсы должны корректно освобождаться.


Метод finalizeState()

Перед удалением слоя вызывается:

finalizeState()

Это финальная стадия жизненного цикла.

Пример:

finalizeState() {
  this.state.texture?.delete();
  this.state.model?.delete();
}

Задачи метода:

  • освобождение памяти GPU;
  • удаление текстур;
  • уничтожение буферов;
  • закрытие соединений с внешними ресурсами.

Если не выполнять очистку, возможно накопление утечек памяти.


Последовательность вызовов методов

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

constructor
    ↓
initializeState
    ↓
updateState
    ↓
draw

При изменении данных:

shouldUpdateState
    ↓
updateState
    ↓
draw

При удалении:

finalizeState

Полный цикл:

Создание
    ↓
initializeState
    ↓
updateState
    ↓
draw
    ↓
updateState
    ↓
draw
    ↓
updateState
    ↓
draw
    ↓
finalizeState

Взаимодействие жизненного цикла с React

При использовании Deck.gl совместно с React жизненный цикл слоя не совпадает с жизненным циклом React-компонента.

Пример:

<DeckGL layers={[layer]} />

При каждом рендере React может создаваться новый экземпляр слоя:

const layer = new ScatterplotLayer({...});

Однако Deck.gl сравнивает новый экземпляр со старым по идентификатору:

id: 'cities'

Если идентификатор совпадает, происходит обновление существующего внутреннего состояния, а не полное пересоздание GPU-ресурсов.

Это позволяет эффективно использовать декларативный подход React без существенных потерь производительности.


Рекомендации по проектированию пользовательских слоёв

Минимизировать работу в updateState()

Тяжёлые вычисления следует выполнять только при необходимости.

if (changeFlags.dataChanged) {
  this.buildSpatialIndex();
}

Использовать updateTriggers

updateTriggers: {
  getColor: theme
}

Это снижает количество лишних пересчётов.


Освобождать ресурсы

finalizeState() {
  this.state.model?.delete();
}

Любой объект GPU должен иметь соответствующую очистку.


Использовать invalidate()

attributeManager.invalidate('colors');

Локальное обновление значительно дешевле полного пересчёта всех атрибутов.


Избегать хранения входных данных в state

Нежелательно:

this.state = {
  data: props.data
};

Предпочтительно:

this.props.data

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


Схема жизненного цикла слоя

Создание слоя
      ↓
initializeState()
      ↓
Сравнение props
      ↓
changeFlags
      ↓
shouldUpdateState()
      ↓
updateState()
      ↓
Обновление атрибутов
      ↓
draw()
      ↓
Рендеринг GPU
      ↓
Изменение данных
      ↓
updateState()
      ↓
draw()
      ↓
Удаление слоя
      ↓
finalizeState()

Понимание этой последовательности является основой разработки производительных визуализаций в Deck.gl, поскольку все механизмы обновления данных, управления памятью и взаимодействия с GPU строятся вокруг описанного жизненного цикла слоя.