跳转至

04-05:React Flow 与布局调试全记录

概述

04-05 这天集中解决了两个布局问题:React Flow 流程图的 dagre 布局异常(画布不显示/节点重叠),以及侧边栏机器人列表的固定列数问题。两者的共同主题是「布局问题的根因往往不在表层,而在数据输入」


第一部分:React Flow + Dagre 布局调试

前置:先修数据,再做图

[13:13] Dad 决策:当前数据层级设计有问题,中间层"开发/运维/健康"意义不大。第二层不一定是 project,也可以是 topic。先修数据,再做 React Flow。

数据修复结果: - 108 条 topic 全部重新分类 - 33 条发生变化,42 条进一步修正 - Agent Portal 从 42 条降到 30 条 - 最终形成 17 个项目组

首次接入 React Flow — 画布不显示

[13:46] 安装 React Flow + dagre,派 Codex 做改造。

[13:59] 验收结果:头部统计正常(10 个进行中 / 108 个主题 / 10 个分类),但画布空白。

排查过程

步骤 检查内容 结果
1 容器高度 ✅ 有高度,约 600px(h-[calc(100vh-18rem)]
2 节点数量 ✅ 154 个节点存在
3 节点坐标 ❌ 第一个节点 y=10579px,极端偏移
4 fitView ⚠️ 在工作,但整体图过大

初步结论:不是"没有节点",而是 dagre 把节点排成超长纵向结构。

fitView 失效的排查

降低 minZoom 到 0.05 后能看到一个节点,但缩放极小。说明 fitView 基于正确的 bounds 计算,但布局本身异常。

尝试过的修复: 1. onInit 手动 fitView — 无效 2. useEffect + requestAnimationFrame — 无效 3. 延长触发延时 — 无效 4. ReactFlowProvider + AutoFitView 子组件 — 无效 5. 切换到"进行中"筛选(10 个 topic / 4 个分类)— 部分可见,但仍重叠

关键判断:fitView 失效是结果不是根因。布局坐标本身错了,fitView 再怎么调都没用。

节点重叠 — 收敛到 dagre 输出异常

[14:08] 打印坐标发现:18 个节点只有 4 个不同坐标。进一步检查 dagre 直接输出:

所有 Category 节点 → (40, 1110)
所有 Project 节点 → (350, 1100)  
所有 Topic 节点   → (700, 1170)

dagre 完全没有把节点垂直分散开。排除了节点 ID 冲突(ID 形如 project:${category}:${project},理论上唯一)。

根因定位 — dagre 节点尺寸对象引用复用

发现getNodeSize 返回的是 NODE_SIZE[type] 的对象引用。dagre 的 setNode 需要独立的 { width, height } 对象。共享引用导致 dagre 内部修改污染所有同类型节点。

错误模式

function getNodeSize(type) {
  return NODE_SIZE[type]  // 返回共享引用
}

修复

function getNodeSize(type) {
  const size = NODE_SIZE[type]
  return { width: size.width, height: size.height }  // 返回新对象
}

结果:流程图渲染正确,三层结构(Category → Project → Topic)清晰可见。

问题与解决方案对照

表象 实际原因 解决
画布不显示 节点堆叠在少数坐标,视觉空白 修复 dagre 尺寸输入
fitView 失效 布局结果本身错误 先修布局,再 fitView
节点重叠 NODE_SIZE[type] 对象引用被复用 每次返回新 {width,height}

第二部分:侧边栏自适应网格布局

问题

[14:19] Dad:左侧机器人列表的列数应该自动变化,当前固定布局太窄了。

现状:使用 Tailwind 固定断点列数:

grid-cols-3 md:grid-cols-4 lg:grid-cols-5

问题原因:这些断点基于 viewport 而不是 sidebar container width。侧边栏不是全屏容器,viewport 断点不适用。

解决方案

改为 CSS Grid 自适应列定义:

grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));

  • auto-fill:根据容器宽度自动填充列数
  • minmax(72px, 1fr):每列最小 72px,有空间则扩展

[14:20] 修改并部署完成。侧栏窄时自动减少列数,宽时自动增加。

历史演进

时间 事件
03-21 Dad 首次提出:拖拽宽度后格子数应自适应,默认 4 格
03-21 首版网格上线,发现窄 sidebar(288px)下 minmax(68px) 退化为 1 列
03-23 Dad 设计审查:有空格,应该自适应
03-26 分支合并时从 auto-fill 回退到固定 grid-cols-*(临时处理)
04-05 最终修复:恢复 auto-fill + minmax(72px, 1fr)

调试方法论总结

从整个布局调试过程中沉淀的排查清单:

React Flow 画布不显示

  1. 检查父容器是否有明确高度
  2. 检查 nodes.length > 0
  3. 打印节点坐标,看是否落在极端范围
  4. 检查布局引擎输出是否合理
  5. 先排除缓存/旧 bundle/Service Worker

fitView 失效

  1. 先打印节点坐标范围,确认布局结果是否正确
  2. 检查 minZoom 是否过大
  3. 布局错误时,修布局而不是调 fitView

dagre 节点重叠

  1. 检查 setNodewidth/height 是否正确
  2. 检查是否复用了共享对象引用(最常见根因)
  3. 检查节点 ID 是否唯一
  4. 直接打印 dagre 输出坐标

核心经验

不要把布局问题当视觉问题。React Flow "不显示"很多时候不是 CSS,而是布局坐标已经坏了。 fitView 失效通常是结果,不是根因。 dagre 最怕共享可变对象 — return { ...NODE_SIZE[type] } 是关键。


里程碑

  • 04-05 13:18 — Dad 决定先修数据再做 React Flow
  • 04-05 13:44 — 108 条 topic 数据修复完成
  • 04-05 13:59 — React Flow 首次接入,确认画布不显示是布局问题
  • 04-05 14:08 — 定位根因:dagre 节点尺寸对象引用复用。修复后流程图正常渲染
  • 04-05 14:20 — 侧边栏改为 auto-fill + minmax 自适应布局