Среда
- reactflow: ^11.x
- dagre: ^0.8.5
- React: ^18.x
Когда я нажимаю кнопку свернуть (P- или C-) на узле:
- Узлы каждый раз удаляются правильно
- Ребра иногда остаются видимыми, плавая без исходных/целевых узлов
- Это происходит периодически - примерно Частота сбоев 60 %
Все ребра, соединенные с удаленными узлами, должны очищаться последовательно в 100 % случаев.
Структура кода
Логика удаления узла (свернуть)
Код: Выделить всё
const handleExpand = useCallback(
async (type, nodeId, action) => {
if (action === 'collapse') {
let nodesToRemove = [];
if (type === 'parent') {
nodesToRemove = collectAncestors(nodeId, edgesRef.current || []);
} else {
nodesToRemove = collectDescendants(nodeId, edgesRef.current || []);
}
if (nodesToRemove.length > 0) {
const nodesToRemoveSet = new Set(nodesToRemove);
// Clear manual positions
manualPositionsRef.current.clear();
manualPositionCoordsRef.current.clear();
// Work with current state
let workingNodes = [...nodes];
let workingEdges = [...edges];
// 1. Filter out removed nodes
workingNodes = workingNodes.filter((n) => !nodesToRemoveSet.has(n.id));
// 2. Filter out edges connected to removed nodes
workingEdges = workingEdges.filter(e => {
const sourceRemoved = nodesToRemoveSet.has(e.source);
const targetRemoved = nodesToRemoveSet.has(e.target);
return !sourceRemoved && !targetRemoved;
});
// 3. Update collapsed node flags
workingNodes = workingNodes.map((n) =>
n.id === nodeId
? {
...n,
data: {
...n.data,
...(type === 'child'
? { childrenExpanded: false }
: { parentExpanded: false }
)
}
}
: n
);
// 4. Apply dagre layout
const { nodes: layoutedNodes, edges: layoutedEdges } = getDagreLayout(
workingNodes,
workingEdges,
'LR'
);
// 5. Update state
setNodes(layoutedNodes);
setEdges(layoutedEdges);
}
// Update expanded state
setExpandedNodes((prev) => ({
...prev,
[nodeId]: { ...prev[nodeId], [type]: false }
}));
return;
}
// ... expand logic
},
[nodes, edges, /* other deps */]
);
Код: Выделить всё
const collectAncestors = (startId, edgesArr) => {
const result = new Set();
const q = [startId];
while (q.length) {
const cur = q.shift();
edgesArr.forEach((e) => {
if (e.target === cur && !result.has(e.source)) {
result.add(e.source);
q.push(e.source);
}
});
}
result.delete(startId); // Don't remove the clicked node
return Array.from(result);
};
const collectDescendants = (startId, edgesArr) => {
const result = new Set();
const q = [startId];
while (q.length) {
const cur = q.shift();
edgesArr.forEach((e) => {
if (e.source === cur && !result.has(e.target)) {
result.add(e.target);
q.push(e.target);
}
});
}
result.delete(startId);
return Array.from(result);
};
Код: Выделить всё
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
// Refs to avoid stale closures
const edgesRef = useRef(edges);
useEffect(() => {
edgesRef.current = edges;
}, [edges]);
- Вложенный setState – вызывается setEdges внутри обратного вызова setNodes (вызывает проблемы с синхронизацией)
- Отдельные вызовы setState – сначала обновляются узлы, затем ребра (по-прежнему) непоследовательно)
- setTimeout с перерасчетом макета – добавлено асинхронное обновление макета (худшие результаты)
- Функциональный setState – использован шаблон setNodes(prev => ...) (та же проблема)
- Текущий подход – работа со снимками состояния, фильтрация всего, а затем обновление обоих одновременно (по-прежнему 40 % успеха)
- Почему очистка границ работает непоследовательно? Существует ли состояние гонки с внутренним состоянием ReactFlow?
- Есть ли правильный способ атомарного обновления узлов и ребер в ReactFlow для обеспечения согласованности?
- Должен ли я использовать другой шаблон для удаления узлов и связанных с ними ребер?
- Может ли пересчет макета dagre мешать ребрам очистка?
- График может иметь сложные отношения (несколько родителей, несколько дочерних элементов)
- Мы используем настраиваемые типы узлов с кнопками развернуть/свернуть
- Позиционирование узлов вручную отслеживается отдельно (очищается при сворачивании)
- Тип использования ребер: 'bezier' с пользовательским стилем
Подробнее здесь: https://stackoverflow.com/questions/798 ... sing-nodes