规模化开发
在前面的几页里,我们一直故意使用很小的例子。这样做的目的不是回避真实问题,而是让你先掌握 Ansiq 的主链路:
signal / computed / effect- 组件与
view! - focus / input / layout
- viewport / history / patch emission
但当你的应用从一个例子变成一个长期运行的终端程序时,新的问题会出现:
- 场景越来越多,单文件开始膨胀
- 系统采样、网络请求、业务状态和 UI 组装混在一起
- subtree replacement 需要保留越来越多的交互态
- viewport 与 history 的语义开始和产品体验强耦合
这一页讨论的不是“怎么把文件夹分层得很漂亮”,而是:当应用变大时,哪些东西应该继续留在一起,哪些东西必须拆开。
一个常见的错误起点
很多终端应用一开始都像下面这样长大:
fn render(&mut self, cx: &mut ViewCtx<'_, Msg>) -> Element<Msg> {
// 1. 读取系统状态
// 2. 修改业务状态
// 3. 根据状态拼装 widgets
// 4. 处理各种 if/else 场景切换
}它在很小的时候看起来没有问题,因为所有逻辑都在一个地方,改起来很快。
但一旦应用开始有:
- 多个页面或 pane
- 后台任务
- 历史列表和 live viewport
- 复杂 focus / routing
- 真实系统采样或网络 IO
这种结构就会变得很难继续维护。最典型的症状是:
- 改一个布局,状态机一起坏
- 改一个 sampler,UI 也要跟着改
- 某个组件树重排后,focus 和 scroll 变得难以解释
Ansiq 应用在规模化时应该拆成什么
Ansiq 里最稳的一种拆法是四层:
app shell / scenariodomain statecomposition / widgetsservices / samplers / loaders
它们的职责应该像这样分开。
1. App shell 负责“这个场景长什么样”
这一层回答的是:
- 这个应用有几个主要区域
- 哪个 pane 是 header / body / footer
- 当前路由或 tab 是什么
- 哪个区域应该获得焦点
它不应该直接做:
- 系统命令解析
- 网络请求
- 复杂的业务状态转换
例如 activity_monitor 这个示例里,场景层应该只负责:
- 顶部
Tabs - 中间进程表
- 底部摘要面板
它不应该自己解析 ps、top、vm_stat 的原始输出。
2. Domain state 负责“应用真正知道了什么”
这一层回答的是:
- 当前选择了哪个进程
- 当前 tab 是 CPU 还是 Memory
- 当前 OpenAPI 文档里选中了哪个 operation
- 当前会话里已经完成了哪些 turn
这一层建议尽量用:
signalcomputed- 少量
effect
而不是散落的可变字段和临时缓存。
一个很好的判断标准是:
如果去掉 UI,剩下来的状态和规则还能否被清楚描述?
如果答案是否定的,通常说明 domain state 和 UI 组装写得太紧了。
3. Composition / widgets 负责“把状态翻译成界面”
这一层是 Ansiq 文档里最容易被忽略,但在长期项目里最重要的一层。
它的作用不是发明新的业务逻辑,而是把更低阶的 widget 组合成更稳定的界面结构。
例如:
SessionHeaderComposerBarBottomPaneSessionTranscript
这些都不是 runtime 内核,但它们非常适合被做成复合组件,而不是在每个场景里手拼一遍。
当项目变大时,很多“看起来只是几个 Box 组合一下”的结构,最后都会演变成值得复用的 composition。
4. Services / samplers / loaders 负责“从外部世界拿数据”
这一层包括:
- 系统监控采样
- 文件加载
- 远程 URL 拉取
- OpenAPI 解析
- 历史数据持久化
它们不应该依赖 widget,也不应该知道 layout、focus、viewport。
最理想的形态是:
- 输入:系统环境、文件、网络
- 输出:纯数据模型
这样场景层就可以只消费结构化数据,而不是在 render 期间临时做解析。
一个更现实的成长路径
下面这条路径比“一开始就设计完整架构”更适合 Ansiq:
第一步:从一个很小的 example 开始
先像 list_navigation 这样做一个真实但小的应用:
- 有一块主内容
- 有一个选中项
- 有一个 footer
这个阶段最重要的是把:
signalview!- focus 和 routing
这三件事吃透。
第二步:抽出 app shell
当你开始反复写:
- header / body / footer
- left pane / detail pane
- top tabs / content / bottom info
就应该抽出 app shell,而不是继续在 render() 里堆树。
第三步:把系统相关代码移到 service/sampler
如果你的示例开始依赖:
psnettop- 远程 URL
- 文件解析
这些逻辑都应该从场景层移出去。
这样做的收益是:
- 测试更容易
- UI 更稳定
- 文档也更容易写
第四步:再考虑高级 runtime 行为
只有当你的应用真的需要时,再去做这些事情:
- continuity keys
- focus scope
- viewport / history 策略
- 更细的 subtree replacement
不要在还没有实际场景之前,就把这些机制塞进每一个小 app。
推荐目录结构
一个中等规模的 Ansiq 应用可以长成这样:
src/
app/
mod.rs
shell.rs
messages.rs
domain/
state.rs
actions.rs
widgets/
session_header.rs
composer_bar.rs
process_table.rs
services/
sampler.rs
loader.rs
parser.rs
scenarios/
activity_monitor.rs
openapi_explorer.rs这里的关键不是目录名,而是依赖方向:
services不依赖widgetswidgets不依赖servicesscenarios负责把几层接起来
什么时候应该抽出新的 composition
可以用下面这个经验规则判断:
如果某一段 UI 同时满足两条,就值得抽:
- 在两个以上场景里出现
- 自己已经带有明确语义,而不只是“几个 Box”
例如:
- “底部输入区”
- “会话头部”
- “分栏 detail explorer”
- “摘要卡片组”
这些都比“把 Block、Paragraph、List 写得更短一点”更值得抽。
规模化时最常见的错误
把 runtime 细节当业务逻辑写
例如把:
- focus 修复
- continuity key
- viewport 重锚
直接写进业务状态机里。
这通常会让业务逻辑越来越难懂。
把业务状态写进 widget 组合件
如果一个组合件开始关心:
- 什么时候拉取数据
- 怎么解析外部协议
- 哪个后台任务该重试
那它通常已经不只是 widget 组合件了。
过早做“全局架构”
很多项目在还没有两个真实场景之前,就开始设计过大的抽象层。
Ansiq 更适合的做法是:
- 先让小例子成立
- 再抽出复用模式
- 再沉淀进 runtime 或 widgets
这一页的真正结论
规模化开发不是“把文件分得更细”,而是要持续保持这条边界:
- runtime 负责更新、布局、patch、session
- app shell 负责场景结构
- domain state 负责应用语义
- widgets/compositions 负责界面翻译
- services 负责外部数据
只要这条边界还清楚,Ansiq 应用就能继续长大而不失控。
最后可以回到 最佳实践 做一次收束,把这一页的结构建议压缩成日常工程规则。