В архитектуре Vega реактивность строится вокруг
сигналов — именованных значений, которые могут зависеть
от данных, пользовательских событий и других сигналов. Каждый сигнал
описывается декларативно и включает несколько ключевых полей, среди
которых центральную роль играет update.
Сигнал в Vega имеет структуру:
{
"name": "width",
"value": 400,
"update": "containerSize()[0]"
}
Поле update определяет выражение, которое пересчитывает
значение сигнала при изменении зависимостей. В отличие от
value, которое задаёт начальное состояние,
update делает сигнал динамическим.
Ключевой принцип:
value — фиксированная инициализацияupdate — реактивное пересчитываемое выражениеupdateПоле update представляет собой выражение на языке
выражений Vega (expression language), которое может включать:
Пример зависимости от другого сигнала:
{
"name": "height",
"value": 300,
"update": "width * 0.75"
}
Здесь height автоматически пересчитывается при изменении
width.
Одна из ключевых возможностей update — реакция на
пользовательские события.
{
"name": "mouseX",
"value": 0,
"on": [
{
"events": "mousemove",
"update": "eventX()"
}
]
}
Хотя здесь используется поле on, логика та же: каждое
событие приводит к вычислению выражения update, которое
перезаписывает значение сигнала.
Важная особенность: update выполняется
в момент наступления события и может использовать объект
event, доступный в контексте выражения.
value, bind и updateСигналы в Vega часто комбинируются с привязками интерфейса:
{
"name": "threshold",
"value": 10,
"bind": {"input": "range", "min": 0, "max": 100},
"update": "clamp(threshold, 0, 100)"
}
Здесь происходит наложение нескольких механизмов:
bind связывает сигнал с UI-элементомupdate корректирует значение при изменении других
сигналовvalue задаёт начальное состояниеИерархия обновления:
updateКаждый сигнал с update становится узлом реактивного
графа зависимостей. Vega строит граф, в котором:
Пример цепочки:
{
"name": "a",
"value": 10
},
{
"name": "b",
"update": "a * 2"
},
{
"name": "c",
"update": "b + 5"
}
При изменении a происходит каскадное обновление:
a → b → c
update в контексте
данныхХотя поле update чаще ассоциируется с сигналами,
динамическое обновление данных реализуется через API Vega View, где
используется похожая модель реактивности.
view.change('table',
vega.changeset()
.insert([{x: 1, y: 2}])
).run();
Здесь реактивность уже не декларативная, но концептуально связана с
update: любое изменение данных запускает пересчёт
визуализации.
В выражениях update доступны функции Vega:
{
"name": "opacity",
"value": 1,
"update": "clamp(scale('opacityScale', datum.value), 0, 1)"
}
Особенности выражений:
scale()datumeventupdate и
взаимодействие с мышьюТипичный пример интерактивного управления:
{
"name": "dragX",
"value": 0,
"on": [
{
"events": "mousedown",
"update": "x()"
},
{
"events": "drag",
"update": "dragX + eventX() - x()"
}
]
}
Логика здесь основана на инкрементальном обновлении:
update сохраняет новое состояниеupdate в
Vega-LiteVega-Lite абстрагирует сигналы, но при компиляции генерирует
Vega-конструкции с update.
Например, интерактивное выделение:
{
"params": [
{
"name": "brush",
"select": "interval"
}
]
}
После компиляции Vega-Lite создаёт сигналы, где update
управляет:
Таким образом, update остаётся внутренним механизмом
реактивности, скрытым за декларативным API.
Сигналы с update часто используются для управления
визуальными параметрами:
{
"name": "colorDomain",
"update": "domain('colorScale')"
}
Или для вычисляемых параметров:
{
"name": "radius",
"update": "sqrt(datum.value) * 5"
}
Это позволяет отделить данные от логики отображения, сохраняя реактивную природу.
Система update оптимизирует пересчёты через граф
зависимостей. При изменении сигнала пересчитываются только зависимые
узлы.
Пример:
{
"name": "base",
"value": 1
},
{
"name": "mid",
"update": "base + 10"
},
{
"name": "top",
"update": "mid * 2"
}
Изменение base приводит к пересчёту только затронутой
цепочки, без полного рендеринга всех элементов.
updateПоле поддерживает условную логику:
{
"name": "state",
"value": "idle",
"update": "hover ? 'active' : 'idle'"
}
Или более сложные конструкции:
{
"name": "size",
"update": "selected ? 20 : (hover ? 12 : 8)"
}
Такие выражения позволяют реализовывать поведение интерфейса без imperative-кода.
Хотя update работает внутри декларативного описания,
внешний JavaScript API может изменять сигналы напрямую:
view.signal('threshold', 42).run();
После этого система автоматически:
update-выраженияИспользование update превращает Vega в реактивную модель
состояния:
update заменяет функции пересчётаПример состояния интерфейса:
{
"name": "zoom",
"value": 1,
"on": [
{
"events": "wheel!",
"update": "clamp(zoom * pow(1.001, event.deltaY * -1), 0.5, 10)"
}
]
}
updateНесмотря на гибкость, выражения имеют ограничения:
Это обеспечивает предсказуемость реактивной модели и упрощает оптимизацию.
{
"name": "scale",
"value": 1,
"on": [
{
"events": "wheel",
"update": "scale * 1.1"
}
]
}
{
"name": "mode",
"value": "line",
"on": [
{
"events": "@toggle:click",
"update": "mode === 'line' ? 'bar' : 'line'"
}
]
}
{
"name": "cursorY",
"on": [
{
"events": "mousemove",
"update": "eventY()"
}
]
}
update в архитектуре реактивных визуализацийПоле update является ключевым механизмом,
связывающим:
В совокупности это формирует декларативную модель, где логика изменения состояния описывается не императивно, а через выражения зависимости и пересчёта.