Края не удаляются последовательно при сворачивании узлов.Javascript

Форум по Javascript
Ответить
Anonymous
 Края не удаляются последовательно при сворачивании узлов.

Сообщение Anonymous »

Я создаю визуализацию диаграммы процесса с помощью ReactFlow с расширяемыми/свертываемыми узлами. При свертывании узлов (удалении предков или потомков) ребра, соединенные с удаленными узлами, очищаются непоследовательно — корректно работает только примерно в 4 случаях из 10.
Среда
  • 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
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Javascript»