设计原理
手册其余部分讲框架做什么;本章收集为什么。每节给出一个选择、它取代的替代方案,以及体现在公共行为里、应用作者可以依赖的后果。这些选择都是已实现的,不是提案:点名替代方案时,说的就是实现有意不走的设计。
保留,而非重建
Section titled “保留,而非重建”View 编译一次成为保留段树,状态写入沿依赖边走到拥有受影响节点的绑定。Ebox 然后只更新受损布局、只重写改变的行。它取代的替代方案是 React 式的模型:每次渲染产生一棵新的候选树,再与上一棵做 diff。
保留让一次更新的代价与变化量成正比,而不是与界面大小成正比:keyed 列表移动节点而不是重建它们,纵向堆叠里的一次文本变化只重合成受影响的行。这也是身份成为一等概念的原因:稳定的种类与 key 让段、它的 Component 实例与 Ebox 节点存活,所以状态在重排中保留。代价是和解必须拥有身份,视图与组件两章完整描述这一点。
一张响应式图,三个相位
Section titled “一张响应式图,三个相位”ref、computed 与 effect 构成一张图。写入把直接观察者标为 dirty、把传递的下游标为 check,所以一个副作用只在它真正读过的值变化时运行——而不是因为某处的某个源变化了。它取代的替代方案是粗粒度失效,让消费者的消费者也重跑;或者一个全局事件总线,让所有订阅者重渲染。
这张图还固定了可见工作的时序。副作用分三个相位运行——pre 侦听器、render 绑定、post 侦听器——Runtime 在 render 与 post 之间提交,所以一次提交恰好写一次缓冲区,post 侦听器读到已发布的结果。批处理把投递延后到最外层边界,所以一个写多个 ref 的事件回调只产生一次提交。提交是原子的:失败的 render 副作用被回滚并搁置,而应用的写入保留,所以持续失败会保住上一次好的 UI,而不是把新 UI 发布一半。一次更新的完整追踪一章走这条链。
Ebox 是唯一的布局与缓冲引擎
Section titled “Ebox 是唯一的布局与缓冲引擎”物理节点树、计算样式、布局、焦点、原生输入与缓冲文本都属于 Ebox;ETAF 只存索引与语义属性,从不保留第二份拷贝。它取代的替代方案是让 ETAF 保留一份并行的几何缓存,或者让应用直接调引擎去拿框架还没暴露的事实。
唯一权威意味着查询不可能与屏幕上的东西不一致,布局或输入行为的一次修复只做在一个地方。应用与目录组件只用 ETAF 公共接口这条边界规则保证了这一点;当确实缺少某个能力时,扩展框架一章讲怎样把新的 Ebox 能力经 ETAF 暴露出来,而不是绕过它。
样式属于 ECSS
Section titled “样式属于 ECSS”选择器匹配、层叠、来源、层与计算值都在通用的 ECSS 库里,Ebox 把自己的样式词表注册给它。它取代的替代方案是引擎内部一套只能用在这些盒子上的私有样式 pass。
把层叠分开让它可复用——一个程序可以给控件、诊断或文档用自己的 schema 计算样式——并且给引擎一个归一化的输入,而不是一堆特例。specified 与 active 的区分出于同样的理由:指定了但无效的声明,与生效的声明不是一回事,报告或调试一个值的消费者需要把它们分清。值与计算样式接口两章分别定义这两半。
flex 与 grid 子集,而不是完整 CSS
Section titled “flex 与 grid 子集,而不是完整 CSS”布局引擎实现文本界面用到的 flex 与 grid 概念——主轴与交叉轴、grow 与 shrink、gap、轨道、span、对齐——其余 CSS 内容一概不做。它取代的替代方案是追求完整 CSS 兼容,连同它许多互相作用的尺寸规则与遗留怪癖。
一个有文档化盒模型的子集在代价上可预测,并且诚实地说明它做不到什么:Ebox 实现不了的 kp 断行模式被记为与 word 相同,而不是悄悄近似;宽过一个像素的边框被拒绝,而不是画错。这种诚实也有代价:有些文档无法被精确复现,参考各章在适用处会写明。
Component 是唯一的可复用 UI 单元
Section titled “Component 是唯一的可复用 UI 单元”可复用的控件就是 Component;没有并行的 widget 或 control 模型及其自己的生命周期。它取代的替代方案是在组件模型旁边再立一套控件类继承,那会需要另一套 props、状态、样式与事件规则。
一个模型意味着所有东西共用一套 prop、slot 与 Theme 规则,目录能组合组件,正是因为它们共享这些规则。其中有两个所有权选择是有意为之:slot 的内容保留调用方的 Context,而包装组件提供的 Context 到达它渲染出的后代,却不到达经它 slot 传入的内容——所以包装者不能劫持不是它创建的内容的环境。Context与组件样式与主题两章定义这套机制。
交互经已提交的 Host 引用流动
Section titled “交互经已提交的 Host 引用流动”事件、焦点与键位派发都作用在已提交的 Host 引用上。Ebox 拥有原生命中测试、焦点顺序、拖拽跟踪与输入会话;ETAF 校验 Runtime 与 Host 引用,并在批处理里运行回调。它取代的替代方案是一个带自己目标模型的全局事件总线。
经已提交引用路由,意味着回调作用在用户真正看到的 Host 上,而不再解析的引用会大声失败,而不是作用在陈旧的节点上。作用域键层建立在同一条规则上:一个键由声明它的最内层已提交 Host 拥有,框架默认只在没有东西认领该键的地方补位。事件与键盘两章定义细节。
Data、Resource 与异步共享一个边界
Section titled “Data、Resource 与异步共享一个边界”Data Source 是一份能力 plist,Controller 拥有它之上的响应式状态;Data 与 Resource 共用一套异步生命周期。它取代的替代方案是内建加载与写入的 store,或者一个与响应式图分离的 promise 库。
这种分离让一个后端可以被不同的控制器复用,也让控制器的查询、分页、选择与状态留在 ETAF,与其余响应式状态在一起。它同时强制把确定性说清楚:已提交的外部写入绝不被刷新重放,未知的结果保持未知,而不是被报告成已回滚。数据控制器与和解两章定义这些规则。
动画与视频是普通子系统
Section titled “动画与视频是普通子系统”过渡与动画是值随时间移动的 ref,视频帧是保留的 Host 内容。它取代的替代方案是第二条渲染路径,带自己的时钟与自己的绘制循环。
架在响应式图上意味着一次动画 tick 就是一次批处理、一次提交与一次 Ebox flush,没有超出已定义范围的新顺序规则,而 Scope 拥有的动画随 Scope 停止。把帧作为内容发布复用与任何其它图片相同的损坏、裁剪与浮层行为。动画与原生视频两章定义这些接口。
显式、可选的观测
Section titled “显式、可选的观测”计时与诊断经一个小观察口传递,它在一次 operation 期间发出扁平报告;性能记录器是这些报告的消费者,不是 advice 注册表,也不是自己的计时路径。它取代的替代方案是全局给框架函数加 advice,那会测到碰巧运行的东西,说不清属于哪次 operation。
报告契约让被记录的工作可归属:一条 operation 有 kind、label 与身份,每个 stage 点出自己的 provider 与类别。记录按 Runtime 选择加入,所以应用只为开启的那段时间付仪器成本,关闭后无残留地回到未观察路径。性能工程一章定义记录器。
测试与文档也是契约
Section titled “测试与文档也是契约”包维护的场景连同可观察的目的被清点出来,只测公共接口,手册自己的门禁检查签名、覆盖与两种语言。它取代的替代方案是绑定实现细节的测试,以及会与代码漂移的文档。
对着公共行为写的测试能在保持行为的重构中存活,并在契约变化时失败,而那正是维护者想要失败的时刻。手册同理:公共接口没有条目、签名不再与 Commentary 一致、或翻译落后,都会让构建失败,所以手册无法悄悄落后于代码。扩展框架一章描述怎样确立正确性、改动又怎样交付。