Разработка на JS (Canvas): Как исправить баг в TreeCanvas, когда корень в UI не совпадает с layoutCenterId при выборе нового узла?
Этот баг возникает в ситуации, когда пользователь выбирает новый узел дерева, а визуальный корень (то, что отображается как центр дерева в Canvas) не обновляется синхронно с внутренним состоянием `layoutCenterId`. Рассмотрим причины и способы исправления.
## Причины несовпадения
1. **Асинхронное обновление состояния.** Если `layoutCenterId` обновляется через `setState` или аналогичный механизм, а перерисовка Canvas происходит до завершения обновления, дерево рендерится со старым центром.
2. **Отсутствие явной передачи нового центра в функцию layout.** Функция расчёта координат узлов (`layoutTree` или аналогичная) может брать `layoutCenterId` из замыкания или устаревшего ref, а не из актуального аргумента.
3. **Кэшированные координаты узлов.** Если координаты узлов кэшируются и не инвалидируются при смене центра, старый корень продолжает использоваться для позиционирования.
4. **Разделение источников истины.** UI-компонент и Canvas-слой могут хранить разные копии «текущего корня», которые не синхронизируются.
## Пошаговое исправление
### Шаг 1: Убедитесь, что layoutCenterId передаётся явно
js
function renderTree(canvas, nodes, layoutCenterId) {
const layout = computeLayout(nodes, layoutCenterId); // явная передача
drawTree(canvas, layout);
}
Никогда не читайте `layoutCenterId` из глобальной переменной или замыкания внутри функции рендера.
### Шаг 2: Синхронизируйте обновление состояния и перерисовку
js
function onNodeSelect(newNodeId) {
setLayoutCenterId(newNodeId); // обновляем состояние
requestAnimationFrame(() => {
renderTree(canvas, nodes, newNodeId); // рендерим с актуальным значением
});
}
Используйте `requestAnimationFrame` или `useEffect` (в React), чтобы гарантировать, что рендер происходит после обновления состояния.
### Шаг 3: Инвалидируйте кэш координат
js
function onNodeSelect(newNodeId) {
layoutCache.clear(); // или layoutCache[prevCenterId] = null
setLayoutCenterId(newNodeId);
}
### Шаг 4: Единый источник истины
Храните `layoutCenterId` в одном месте (например, в центральном store или ref) и подписывайте на него как UI-компонент, так и Canvas-рендерер:
js
const layoutCenterRef = useRef(rootId);
function onNodeSelect(newNodeId) {
layoutCenterRef.current = newNodeId;
redrawCanvas();
}
### Шаг 5: Отладка — логируйте оба значения
js
console.log(‘UI root:’, uiRootId, ‘layoutCenterId:’, layoutCenterId);
Это поможет точно локализовать момент расхождения.
## Итог
Главное правило: **layoutCenterId должен быть единственным источником истины**, передаваться явно в функцию рендера и обновляться до начала перерисовки Canvas. Устранив асинхронность и кэширование, вы синхронизируете визуальный корень дерева с внутренним состоянием.
