Как в React Flow реализовать логику соединений (junction), чтобы стрелки веток корректно привязывались к коннекторам (childHandleOffsets)?
Реализация логики соединений (junction) в React Flow — одна из нетривиальных задач, особенно когда требуется, чтобы стрелки ветвящихся путей корректно привязывались к кастомным коннекторам с динамическими смещениями (childHandleOffsets).
## Основная концепция
Junction-узел — это специальный узел-«развилка», от которого расходятся несколько рёбер (edges). Чтобы стрелки корректно стартовали из нужных точек, необходимо:
1. **Создать кастомный узел** с несколькими « компонентами.
2. **Вычислить смещения** для каждого дочернего коннектора (childHandleOffsets).
3. **Передать позиции** через `data` или `style` в компонент узла.
## Шаг 1: Кастомный Junction-узел
jsx
import { Handle, Position } from ‘reactflow’;
const JunctionNode = ({ data }) => {
const { childHandleOffsets = [] } = data;
return (
{/* Динамические исходящие коннекторы */}
{childHandleOffsets.map((offset, index) => (
))}
);
};
## Шаг 2: Вычисление childHandleOffsets
Смещения вычисляются на основе количества дочерних веток и желаемого расстояния между ними:
js
function computeChildHandleOffsets(childCount, spacing = 20) {
const offsets = [];
const totalHeight = (childCount — 1) * spacing;
const startY = -totalHeight / 2;
for (let i = 0; i < childCount; i++) {
offsets.push({ x: 0, y: startY + i * spacing });
}
return offsets;
}
## Шаг 3: Передача данных в узел
js
const nodes = [
{
id: 'junction-1',
type: 'junctionNode',
position: { x: 300, y: 200 },
data: {
childHandleOffsets: computeChildHandleOffsets(3),
},
},
];
## Шаг 4: Привязка рёбер к коннекторам
js
const edges = [
{ id: 'e1', source: 'junction-1', sourceHandle: 'output-0', target: 'node-a' },
{ id: 'e2', source: 'junction-1', sourceHandle: 'output-1', target: 'node-b' },
{ id: 'e3', source: 'junction-1', sourceHandle: 'output-2', target: 'node-c' },
];
## Регистрация кастомного узла
jsx
const nodeTypes = { junctionNode: JunctionNode };
## Важные нюансы
— **Уникальность id хэндлеров**: каждый `Handle` должен иметь уникальный `id` в рамках узла.
— **Пересчёт при изменении дерева**: если количество веток меняется динамически, пересчитывайте `childHandleOffsets` и обновляйте `data` узла через `setNodes`.
— **ELK/Dagre layout**: при автоматической раскладке используйте layouted-позиции для корректного вычисления смещений.
— **isValidConnection**: добавьте валидацию, чтобы к junction-узлу нельзя было подключить неподходящие рёбра.
Такой подход обеспечивает гибкую и масштабируемую логику junction-узлов с корректной привязкой стрелок к каждому коннектору.
