Как в React Flow запретить создание циклических связей между узлами на уровне схемы данных?
Циклические связи в React Flow могут нарушить логику обработки данных, особенно если граф используется для описания зависимостей, пайплайнов или DAG-структур. Чтобы запретить создание циклов на уровне схемы данных, нужно перехватывать попытку добавления нового ребра и проверять, не образует ли оно цикл.
## Основной подход: обработчик onConnect с проверкой цикличности
React Flow предоставляет колбэк `onConnect`, который вызывается при попытке соединить два узла. В этом месте можно реализовать валидацию.
jsx
import { useCallback } from ‘react’;
import ReactFlow, { addEdge, useEdgesState, useNodesState } from ‘reactflow’;
function hasCycle(edges, sourceId, targetId) {
// Строим граф смежности
const adjacency = {};
for (const edge of edges) {
if (!adjacency[edge.source]) adjacency[edge.source] = [];
adjacency[edge.source].push(edge.target);
}
// DFS: проверяем, достижим ли sourceId из targetId
const visited = new Set();
const stack = [targetId];
while (stack.length > 0) {
const node = stack.pop();
if (node === sourceId) return true; // цикл найден
if (visited.has(node)) continue;
visited.add(node);
const neighbors = adjacency[node] || [];
stack.push(…neighbors);
}
return false;
}
export default function FlowEditor() {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
const onConnect = useCallback(
(connection) => {
const { source, target } = connection;
if (hasCycle(edges, source, target)) {
console.warn(‘Циклическая связь запрещена’);
return; // не добавляем ребро
}
setEdges((eds) => addEdge(connection, eds));
},
[edges, setEdges]
);
return (
);
}
## Альтернатива: isValidConnection
React Flow также поддерживает проп `isValidConnection`, который позволяет запретить соединение ещё до его создания — прямо на уровне UI, отключая возможность дропа:
jsx
const isValidConnection = useCallback(
(connection) => {
return !hasCycle(edges, connection.source, connection.target);
},
[edges]
);
Этот подход предпочтительнее, так как пользователь сразу видит, что соединение невозможно (ручка не подсвечивается как допустимая цель).
## Дополнительные рекомендации
— **Уведомляйте пользователя**: при попытке создать цикл покажите toast или tooltip с объяснением.
— **Учитывайте направленность**: алгоритм выше работает для ориентированных графов. Для неориентированных логика проверки отличается.
— **Производительность**: при большом количестве узлов (>1000) DFS может быть медленным. Рассмотрите использование топологической сортировки или библиотеки `graphlib`.
— **Тестирование**: покрывайте функцию `hasCycle` юнит-тестами с граничными случаями: самопетли, длинные цепочки, параллельные рёбра.
Такой подход обеспечивает надёжную защиту от циклов на уровне данных и интерфейса одновременно.
