5.4 案例分析与实战项目 订单管理中台是贯穿本节的实战项目:从一张需求清单出发,用 5.1 的三层骨架落地、5.2 的库边界拆分、5.3 的模式填肉,再用第 3 章的手段做性能收口,最后启用 SSR 与 PWA 上线。主线在每一站各现身一次——第一次点击时的 Zone 哨兵、列表滚动时的 OnPush 跳过、筛选联动时的信号调度、首屏直出时的服务端检查。读完本节,全册的知识在同一个项目里对上了号。 上一节留下的钩子 5.3 结尾说"模式是机制的执行手段",留下的钩子是:这些手段拼在一起,能不能撑起一个真实项目从零到上线的全程?本节用一个订单管理中台作答,动手中需要达成: 按需求清单完成架构决策记录,说出每层选型与主线的关系。 实现订单列表与详情两个页面,覆盖取数、缓存、鉴权、错误重试。
订单管理中台是贯穿本节的实战项目:从一张需求清单出发,用 5.1 的三层骨架落地、5.2 的库边界拆分、5.3 的模式填肉,再用第 3 章的手段做性能收口,最后启用 SSR 与 PWA 上线。主线在每一站各现身一次——第一次点击时的 Zone 哨兵、列表滚动时的 OnPush 跳过、筛选联动时的信号调度、首屏直出时的服务端检查。读完本节,全册的知识在同一个项目里对上了号。
5.3 结尾说"模式是机制的执行手段",留下的钩子是:这些手段拼在一起,能不能撑起一个真实项目从零到上线的全程?本节用一个订单管理中台作答,动手中需要达成:
需求四条:订单列表(分页、按状态筛选、关键词搜索)、订单详情(含操作日志)、角色权限(客服只读、主管可改)、首屏一秒内可见。决策记录只有三行:
ADR-01 分层:data-sdk 仓储层 + order-domain 状态层 + 两个应用,依据 5.1 ADR-02 组件:页面级容器 + 哑组件全部 OnPush,列表用 track 复用行,依据 3.2 / 5.3 ADR-03 渲染:管理端不启用 SSR(内网系统),门户端启用 SSR + PWA,依据 3.7 / 3.8
架构决策记录的价值不是文档本身,而是把"为什么"钉在变更检测主线上:每个决策都对应一次可以复现的测量。列表页的 OnPush 改造有没有用,profiler 里看检查耗时;SSR 值不值得,首页直出时间说话。
// 容器:筛选条件变化 → 仓储取数 → 信号写入 → 检查恰好发生一次 @Component({ selector: 'app-order-list-page', imports: [OrderFilterComponent, OrderTableComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: ` <app-order-filter (filterChanged)="onFilter($event)"/> <app-order-table [rows]="rows()" [loading]="state().phase === 'loading'"/> ` }) export class OrderListPageComponent { private repo = inject(OrderRepository); private state = signal<PageState<Order[]>>({ phase: 'idle' }); rows = signal<Order[]>([]); onFilter(f: OrderFilter) { this.state.set({ phase: 'loading' }); // 状态机:进入加载态 this.repo.query(f).subscribe({ next: list => { // 数据到达:两条信号写入 this.rows.set(list); this.state.set({ phase: 'done', data: list }); }, error: () => this.state.set({ phase: 'error', retry: () => this.onFilter(f) }) }); } }
// 哑组件:行级 OnPush + track 复用,滚动翻页时旧行不重算 @Component({ selector: 'app-order-table', changeDetection: ChangeDetectionStrategy.OnPush, template: ` @for (r of rows(); track r.id) { <tr (dblclick)="open.emit(r.id)"> <td>{{ r.no }}</td><td>{{ r.customer }}</td> <td class="amt">{{ r.amount | number: '1.2-2' }}</td> <!-- 纯管道:引用不变就不重算 --> </tr> } ` }) export class OrderTableComponent { rows = input.required<Order[]>(); open = output<string>(); }
主线在此处完整现身:客服在筛选框敲回车,Zone 哨兵报告一次异步事件结束,Angular 从根往下检查;容器输入新引用触发表格重绘,纯管道按引用跳过、track 命中的行直接复用。一次用户操作对应一轮检查、一轮检查里只有真正变化的绑定被重算——这就是全册反复打磨的那句话的工程形态。
| 指标 | 改造前(默认策略) | 改造后(OnPush + track + 信号) |
|---|---|---|
| 翻页一轮检查耗时 | 8.4 ms | 1.9 ms |
| 检查的组件数 | 全页 47 个 | 9 个 |
| 筛选输入到出结果 | 120 ms | 46 ms |
改造清单全部来自 3.2 与 5.3:容器与哑组件分层、输入只传不可变值、行组件 track 订单编号、筛选防抖 300 毫秒后仍由信号调度。数字本身不惊人,惊人的是方法可复现——任何读者按同一清单改造自己的页面,应能得到同方向的结论。

门户端启用 SSR 后首次有意义绘制从 2.8 秒缩到 0.9 秒,代价是要处理服务端与浏览器差异(3.8 节的 window 报错清单照抄即用);PWA 的 Service Worker 缓存住静态资源,断网时最近一页订单仍可查看——两条延伸各守住一种"检查不发生"的场景:前者让首屏检查提前到服务端完成,后者让离线时连框架都不用启动。验收清单最后一项写给半年后的维护者:profiler 截图与 ADR 存档在同一目录,谁质疑性能,先跑一遍同样的测量。
全册至此收口。回到自己的项目时,建议带着第 3 章的 profiler 截图习惯与本章的 ADR 模板:机制在书里,判断在你手上。