Интеграция Vega и Vega-Lite в React-приложения строится вокруг идеи декларативного описания визуализации через JSON-спецификацию и императивного рендеринга через движок Vega. В экосистеме React это приводит к нескольким устойчивым моделям:
react-vega,
vega-embed)View через жизненный цикл
компонентовVega-Lite выступает высокоуровневым DSL, который компилируется в Vega-spec, после чего уже отрисовывается движком Vega.
Основные зависимости:
npm install react-vega vega vega-lite vega-embed
Типовая связка:
vega — ядро рендерингаvega-lite — декларативный слой описания графиковvega-embed — утилита для вставки графиков в DOMreact-vega — React-обёрткаreact-vega предоставляет компонент
<Vega />, который принимает спецификацию и
автоматически управляет жизненным циклом визуализации.
import { Vega } from 'react-vega';
const spec = {
width: 400,
height: 200,
data: {
values: [
{ x: 1, y: 2 },
{ x: 2, y: 5 },
{ x: 3, y: 3 }
]
},
mark: 'line',
encoding: {
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' }
}
};
export default function Chart() {
return <Vega spec={spec} />;
}
Особенность этого подхода заключается в том, что React не управляет графиком напрямую — он лишь передаёт спецификацию, а Vega самостоятельно обновляет визуализацию при изменении входных данных.
Vega-Lite позволяет писать более компактные описания графиков:
const vlSpec = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
data: { values: [] },
mark: 'bar',
encoding: {
x: { field: 'category', type: 'nominal' },
y: { field: 'value', type: 'quantitative' }
}
};
В React важно учитывать, что любые изменения объекта спецификации приводят к потенциальному пересозданию визуализации. Поэтому используется мемоизация:
import { useMemo } from 'react';
import { VegaLite } from 'react-vega';
function BarChart({ data }) {
const spec = useMemo(() => ({
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
data: { values: data },
mark: 'bar',
encoding: {
x: { field: 'name', type: 'nominal' },
y: { field: 'value', type: 'quantitative' }
}
}), [data]);
return <VegaLite spec={spec} />;
}
vega-embed предоставляет более низкоуровневый контроль и
используется, когда требуется доступ к View или расширенная
настройка.
import { useEffect, useRef } from 'react';
import embed from 'vega-embed';
function VegaContainer({ spec }) {
const containerRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
const result = embed(containerRef.current, spec, {
actions: false
});
return () => result.finalize();
}, [spec]);
return <div ref={containerRef} />;
}
Ключевой момент — обязательное освобождение ресурсов через
finalize(), иначе при частых обновлениях возникает утечка
памяти из-за накопления внутренних View экземпляров.
При прямой работе с Vega API создаётся объект View,
который необходимо явно контролировать:
import { useEffect, useRef } from 'react';
import * as vega from 'vega';
function RawVega({ spec }) {
const ref = useRef(null);
const viewRef = useRef(null);
useEffect(() => {
const runtime = vega.parse(spec);
const view = new vega.View(runtime)
.renderer('canvas')
.initialize(ref.current);
view.run();
viewRef.current = view;
return () => view.finalize();
}, [spec]);
return <div ref={ref} />;
}
Этот подход используется при необходимости:
Частая проблема — полное пересоздание View при каждом
изменении данных. Vega позволяет обновлять dataset напрямую:
useEffect(() => {
if (!viewRef.current) return;
viewRef.current
.change('table', vega.changeset().remove(() => true).insert(data))
.run();
}, [data]);
Такой подход значительно повышает производительность при частых обновлениях (стриминг, дашборды, realtime аналитика).
Vega поддерживает систему событий, которая легко интегрируется с React:
useEffect(() => {
if (!viewRef.current) return;
viewRef.current.addSignalListener('hover', (name, value) => {
console.log(name, value);
});
}, []);
Также можно подписываться на клики по маркерам:
viewRef.current.addEventListener('click', (event, item) => {
console.log(item.datum);
});
При этом важно учитывать, что события живут вне React-цикла, поэтому
синхронизация с состоянием требует аккуратного использования
useCallback и useRef.
Типовой паттерн — связывание состояния с Vega spec:
function Dashboard() {
const [filter, setFilter] = useState('A');
const spec = useMemo(() => ({
data: { values: getData(filter) },
mark: 'line',
encoding: {
x: { field: 'x', type: 'quantitative' },
y: { field: 'y', type: 'quantitative' }
}
}), [filter]);
return (
<>
<button onCl ick={() => setFilter('A')}>A</button>
<button onCl ick={() => setFilter('B')}>B</button>
<Vega spec={spec} />
</>
);
}
Основной принцип — спецификация должна быть функцией состояния, а не мутируемым объектом.
При работе с Vega в React критично избегать:
Используются техники:
const spec = useMemo(() => buildSpec(data), [data]);
const stableData = useMemo(() => data, [JSON.stringify(data)]);
Вместо одного большого spec используется композиция:
Vega требует явного пересчёта размеров:
useEffect(() => {
if (!viewRef.current) return;
viewRef.current.resize().run();
}, [width, height]);
В React часто используется ResizeObserver:
useEffect(() => {
const ro = new ResizeObserver(([entry]) => {
setSize({
width: entry.contentRect.width,
height: entry.contentRect.height
});
});
ro.observe(ref.current);
return () => ro.disconnect();
}, []);
Vega поддерживает темы через конфигурацию:
const spec = {
config: {
axis: {
labelColor: '#666',
titleColor: '#333'
},
style: {
cell: {
stroke: '#ddd'
}
}
}
};
В React удобно хранить тему отдельно:
const theme = useContext(ThemeContext);
const spec = useMemo(() => ({
config: theme.vegaConfig,
data: { values: data },
mark: 'bar'
}), [theme, data]);
Vega напрямую зависит от DOM и canvas, поэтому серверный рендеринг требует изоляции:
const isClient = typeof window !== 'undefined';
return isClient ? <Vega spec={spec} /> : null;
При использовании Next.js или аналогов графики обычно рендерятся только на клиенте.
При масштабировании приложений используется следующая структура:
Пример разделения:
const useChartSpec = (data) =>
useMemo(() => ({
data: { values: transform(data) },
mark: 'area',
encoding: {
x: { field: 'time', type: 'temporal' },
y: { field: 'value', type: 'quantitative' }
}
}), [data]);
import { VisualizationSpec } from 'vega-embed';
const spec: VisualizationSpec = {
mark: 'line',
data: { values: [] }
};
Типизация помогает избегать ошибок в encoding и структуре данных, особенно при динамическом построении графиков.
Vega хорошо подходит для потоковых систем:
useEffect(() => {
const interval = setInterval(() => {
setData(prev => [...prev.slice(-50), generatePoint()]);
}, 1000);
return () => clearInterval(interval);
}, []);
И затем обновление через changeset без пересоздания view
обеспечивает стабильную производительность даже при высокой частоте
обновлений.
Типичные проблемы:
Для диагностики полезно включать:
vega.debug.enable(true);
и анализировать runtime через встроенные инструменты Vega Inspector.