Component 不是 Use Case
Component 是展示元素。
它渲染状态,接收用户交互,并向外发送 Intent。
听起来很简单,但实际项目里往往不是这样。
很多前端应用中的 Component 会在不知不觉中变成“带 Template 的小型 Application Service”:它们校验业务规则、转换数据、调用 API、决定导航、显示 Toast、刷新列表,同时还要维护本地状态。
到了这一步,它就不再只是 Component。
它成了穿着 Component 外衣的 Use Case。

Component 可以做什么
Section titled “Component 可以做什么”Component 可以处理 UI 工作。
例如:
- 展示数据
- 消费 ViewModel
- 持有局部 UI State
- 接收用户交互
- 发送简单的 UI Event
- 根据状态调整展示
Component 可以知道 Dialog 是否打开,可以知道当前激活的是哪个 Tab,可以渲染表单,也可以在 ViewModel 表示某项操作不可用时禁用按钮。
但当用户点击按钮后,业务上到底应该发生什么,不应该由 Component 自己决定。
这才是关键。
常见的漂移过程
Section titled “常见的漂移过程”通常会慢慢变成这样:
@Component(...)export class OrderComponent { constructor( private http: HttpClient, private router: Router, private toast: ToastService, ) {}
async submitOrder(data: OrderData) { const validated = this.validate(data); // Business-Logik const enriched = this.enrich(validated); // Domänen-Transformation
await firstValueFrom( this.http.post('/api/orders', enriched), // Infrastruktur );
this.toast.success('Bestellung gespeichert'); // Seiteneffekt this.router.navigate(['/confirmation']); // Navigation }
private validate(data: OrderData) { // fachliche Regeln }
private enrich(data: OrderData) { // Domänen-Transformation }}乍看之下很高效。
所有东西都在一个地方,流程也很好跟踪:按钮点击,方法执行,结束。
问题只是:这个“一个地方”选错了。
此时 Component 已经知道:
- 业务校验规则
- 数据增强逻辑
- HTTP 基础设施
- Toast 行为
- 导航
- Use Case 的执行顺序
这已经不是展示元素。
这是一个挂着 HTML 的应用流程。

为什么这很危险
Section titled “为什么这很危险”问题不在于 Component “代码太多”。
代码多只是症状。
真正的问题是:它有太多不同的变化原因。
展示变化,Component 要改。 业务规则变化,Component 要改。 API 变化,Component 要改。 导航变化,Component 要改。 Toast 行为变化,Component 还是要改。
这就是不健康的耦合。
不是因为某张架构图会不高兴,而是因为变化不断流向成本最高的地方。
Component 通常更贴近 Framework 和 DOM,测试成本也更高。业务流程恰恰不应该藏在这里。
真正的问题不是:
Component 能不能有方法?
当然可以。
更好的问题是:
这个方法是否在做业务决策?
如果答案是“是”,它大概率就不属于 Component。
下面这种 Component 方法没有问题:
openDetails() { this.detailsVisible.set(true);}这是 UI State。
而下面这种就值得警惕:
submitOrder(data: OrderData) { if (data.items.length === 0) { this.toast.error('Bestellung ohne Positionen ist nicht erlaubt'); return; }
const payload = this.mapToPayload(data); this.http.post('/api/orders', payload).subscribe(() => { this.router.navigate(['/confirmation']); });}这里同时出现了业务规则、Mapping、Infrastructure 和流程编排。
这已经不是 UI 行为了。
Component 发送 Intent
Section titled “Component 发送 Intent”稳健的 Component 不负责描述完整流程。
它只表达意图。
@Component(...)export class OrderComponent { readonly vm = this.facade.vm;
constructor(private readonly facade: OrderFacade) {}
submit(data: OrderData) { this.facade.submitOrder(data); }}Component 只说:
用户想提交一笔订单。
它不需要知道接下来是不是 HTTP Call,不需要知道是否显示 Toast,也不需要知道是否导航或 Reload。
这正是边界的意义。

那么谁来编排
Section titled “那么谁来编排”不是 Component。
根据具体架构,可以由 Facade、Store、Use Case Service 或 Application Layer 承担。
名称没有责任边界重要。
Component -> sendet Intent
Facade / Store -> koordiniert Zustand und UI-nahe Abläufe
Use Case / Application Service -> führt fachlichen Ablauf aus
Infrastructure -> spricht mit API, Storage, externen Systemen或者更紧凑:
Component → Facade → Use Case → InfrastructureComponent 是系统的 UI 边缘。
它不是系统本身。
哪些东西不应该进 Component
Section titled “哪些东西不应该进 Component”作为经验规则,我会尽量把以下内容留在 Component 之外:
- HTTP 调用
- Repository 访问
- DTO Mapping
- 业务校验
- Domain Transformation
- 复杂计算
- Event 编排
- Reload 链
- 作为 Use Case 一部分的 Toast 和导航决策
- 多数据源的手工同步
- 超出简单显示判断的权限逻辑
单独出现一个并不意味着架构灾难。
但当其中几个同时出现时,Component 很可能已经越过自己的职责边界。

哪些东西可以留在 Component
Section titled “哪些东西可以留在 Component”也不是什么都要抽出去。
否则只会变成架构表演。
以下内容完全可以留在 Component:
- Dialog 的局部可见性
- 当前激活的 Tab
- Hover / Focus State
- 简单 UI 交互
- 转发一次按钮点击
- 消费 ViewModel
- 很简单的展示辅助逻辑
例如:
@Component(...)export class OrderDialogComponent { readonly dialogVisible = signal(false);
open() { this.dialogVisible.set(true); }
close() { this.dialogVisible.set(false); }
submit(formValue: OrderFormValue) { this.submitted.emit(formValue); }}这是 UI 行为。
Component 并不决定订单在业务上是否合法,也不决定 API Payload 长什么样,更不决定成功后导航到哪里。
为什么测试会更简单
Section titled “为什么测试会更简单”如果 Use Case 藏在 Component 中,测试就必须穿过 UI Layer。
于是一个测试同时需要处理 DOM、Framework Lifecycle、Service Mock、Router、Toast 和业务规则。
这会很笨重。
如果 Component 只渲染 ViewModel 并发送 Intent,测试就会自然变小:
- Component Test 验证展示
- Use Case Test 验证业务流程
- Mapper Test 验证转换
- Store / Facade Test 验证状态与 Event Flow
每一层都测试自己真正负责的部分。
这不是形式主义。它会让测试更快、更稳定,也更容易读懂。
最重要的区别
Section titled “最重要的区别”Component 可以触发一次交互。
但它不应该拥有完整的业务流程。
这就是下面两段代码的差别:
submit(data) { this.facade.submitOrder(data);}和:
submit(data) { const validated = this.validate(data); const payload = this.map(validated);
this.http.post('/api/orders', payload).subscribe(() => { this.toast.success('Gespeichert'); this.router.navigate(['/confirmation']); this.reload(); });}第一种情况下,Component 表达意图。
第二种情况下,Component 拥有 Use Case。
Component 不是 Use Case。
它们是 UI 的边界。
它们渲染状态,接收交互,并发送 Intent。
但不应该编排业务逻辑、Infrastructure 或完整的业务流程。
好的 Component 往往有点无聊:
ViewModel 进。Intent 出。中间负责 Rendering。这不是“少做架构”。
这恰恰就是架构的意义。