【Canvas × Vue】day85-boundary-analysis

作者:mario 发布时间: 2026-09-04 阅读量:6 评论数:0

Day 85 · 边界分析 — 哪些数据归 Vue,哪些留在画布:动手前的设计日

今天的产出不是代码,是一份边界决策文档——把屏 C 的全部数据逐个过堂:归 Vue、归画布、还是走桥。这份文档是本周的施工图(后面六天都按它干活),也是你第一份"框架边界"的设计记录。边界课的全部价值在今天想清楚:动手后返工边界,代价是重来。


目录


一、屏 C 的数据全盘点

先把屏 C 涉及的所有数据列出来(不管归属,先穷举——第 7 周编辑器 + Day 68 展示态的全部状态):

A. 静态结构类
   1. 拓扑配置(nodes/edges 声明)        —— Day 68 的 topo-config
   2. 节点几何(x/y/w/h)
   3. 图层尺寸(三层画布的 width/height)

B. 实时数据类(来自 store)
   4. 设备状态(ok/warn/danger/offline)
   5. 设备温度(节点副标签)
   6. 告警列表(连线/节点脉冲的触发源)

C. 交互状态类
   7. 视口(scale/offsetX/offsetY)
   8. hover 的节点(详情面板数据源)
   9. 拖拽中间态(pan 手势的 delta)

D. 动画状态类
   10. 告警脉冲相位(0→1 循环)
   11. 帧时间戳(rAF 的 now)

二、三问法则逐项过堂

回忆周总览的三问:(1) 需要被 Vue 组件读吗?(2) 每帧都变吗?(3) 变化触发整图重绘还是局部 UI?

# 数据 问 1(Vue 读) 问 2(每帧变) 问 3(重绘/UI) 裁决
1 拓扑配置 需要(声明式渲染的源头) 加载时整图 Vue(静态,reactive 安全)
2 节点几何 否(只有画布用) 拖拽时高频 整图 画布(renderer 内部持有)
3 图层尺寸 resize 时 整图 画布(resize 协议自治)
4 设备状态 间接(图例计数) 否(2s 快照) 整图重绘 store → 桥入
5 设备温度 hover 详情面板要读 局部(副标签)+ hover UI store → 桥入
6 告警列表 详情面板/弹窗要读 事件级 脉冲动画触发 store(Vue 侧已有)
7 视口 pan/zoom 时每帧 整图 画布(最高频,绝不进 Vue)
8 hover 节点 详情面板是 Vue 组件 否(事件级) 局部 UI(面板内容) Vue(存 id!见坑 2)
9 拖拽中间态 每帧 整图 画布
10 脉冲相位 每帧 局部(动画层) 画布(rAF 循环私有)
11 帧时间戳 每帧 画布

裁决分布的规律:Vue 拿到的全是低频、事件级、需要跨组件消费的;画布私有的全是每帧、高频、纯渲染的。这条规律本身比表格更值钱——记进笔记。


三、桥协议设计

边界定了,桥就是两份显式契约:

3.1 入向桥:Vue → 画布(数据驱动重绘)

/**
 * 入向桥契约(Day 87 实现,今天只定义)
 * 原则:桥口要"窄"——只传画布需要的最小形态,不传整个 store
 */
interface TopoBridge {
  /** 数据快照到达:触发状态层重绘(2s 分频 + 脏检查) */
  update(states: Map<string, DeviceState>, temps: Map<string, number>): void;
  /** resize 通知(DPR 适配内部处理) */
  resize(): void;
  /** 动画开关(失活暂停/激活恢复) */
  setAnimation(on: boolean): void;
}

3.2 出向桥:画布 → Vue(事件上报)

/**
 * 出向桥契约(Day 88 实现)
 * 原则:画布"报告事实",不携带渲染对象——传 id 传数据,不传内部节点引用
 */
interface TopoEvents {
  /** hover 变化:payload 是设备 id 或 null(离开) */
  (e: "hover", id: string | null): void;
  /** 节点点击(未来跳详情用,本周预留) */
  (e: "select", id: string): void;
}

桥的窄口价值:两份契约就是屏 C 的全部耦合面——未来换渲染引擎(如换 WebGL)只动画布侧,Vue 侧零改动;反之升级 Vue 也不碰画布。边界的本质是可替换性的承诺


四、反例的代价推演

4.1 如果视口(#7)进了 Vue

pan 手势 → 每帧改 viewport.value(scale/offset)
  → 每帧触发依赖 viewport 的 watch(假设有 HUD 显示缩放比)
  → 每帧 reactive 深检查 + watch 调度 + 组件 patch
  → 与 rAF 循环叠加:60fps × 2 套调度 = 掉帧
修复成本:数据搬家 + 所有消费点重写(本周重来)

4.2 如果 hover(#8)存了节点对象

renderer 命中检测 → 返回内部节点对象 → 存进 hoverNode.value
  → 对象引用每次可能新建(命中检测若返回拷贝)
  → watch hoverNode 每帧误触发 → 详情面板每帧重渲
修复成本:小(改存 id),但定位半天("面板为什么一直闪")

推演的意义:两个反例都不违反"能跑"的标准,但都违反"能扛"的标准——边界设计的失败不在功能,在性能与可维护性的慢性损耗


五、边界决策文档成文

今天的核心作业——把第二至四节整理成 docs/boundary-screen-c.md 存进 dashboard-vue 工程:

# 屏 C 边界决策文档(Day 85)

## 数据裁决表
(第二节的完整表格,含裁决理由)

## 桥契约
(第三节的两个接口 + 设计原则注释)

## 反例推演记录
(第四节,供未来 review 时对照)

## 复审触发条件
- 设备数 > 200 时:重评"状态层重绘"是否需要脏矩形(第 7 周 Day 46 能力回流)
- hover 详情变复杂(多面板联动)时:考虑 provide/inject 局部上下文

最后一节很重要——边界决策要有失效条件,环境和需求变了边界就要复审,没有一劳永逸的分界线。


六、常见坑点

坑 1:把"边界"当成"墙"

桥走不通时绕路:为了某个小需求直接从画布内部 import store——边界上开洞。纪律:任何数据流动必须走桥契约,桥不够用就扩桥(改接口是显式决策),不开侧门。

坑 2:裁决表写成"感觉"

“这个放画布吧,感觉合适”——没有三问依据的裁决经不起复审。每个裁决必须能回答三问,今天的表格就是范本。

坑 3:静态配置误判为"不用管"

拓扑配置(#1)裁决归 Vue,但它只在加载时消费一次——别顺手做成 computed 派生(无意义的响应式开销)。静态数据用普通常量 + markRaw 亦可,裁决"归 Vue"不等于"必须响应式"。


七、自测挑战

T1 · 数据全盘点 + 三问过堂(60 分钟)

不看本文,自己从零完成第一、二节:先穷举屏 C 全部数据(≥ 10 项),再逐项三问裁决。完成后与本文对照,差异处重点分析——分歧比一致更有学习价值

T2 · 反例代价推演(30 分钟)

自选一个数据(本文未推演的,如"图例计数"),假设放错边,推演代价链。推演能力是设计能力的预演。

T3 · 桥契约评审(30 分钟)

把第三节的接口给"未来的自己"写注释:每方法的触发时机、频率、payload 形态。写不清楚的地方就是设计漏洞——接口注释是设计的试金石


八、总结

环节 要点
穷举 先列全再裁决,防遗漏
三问 Vue 读吗 / 每帧变吗 / 触发什么——裁决的唯一依据
规律 Vue 拿低频事件级,画布留每帧高频
窄口 + 双向契约 = 可替换性承诺
复审 边界要有失效条件,没有永恒分界线

施工图就绪。明天开工:三层画布迁进组件骨架。

评论