Blazor开发:无需类型检查展示POS交易项列表的优雅方案
问题描述
开发POS(销售点)页面UI时,需展示按添加时间排序的交易项列表,交易项包含三种类型:订单、分组订单、自定义项。它们共享Type、Description、Price基础信息,但展示需求不同:
- 订单需显示包含的子项
- 分组订单需显示包含的子订单
- 自定义项需显示数量
当前通过if-else判断类型实现差异化展示,代码不够优雅;且交易项类来自外部类库,无法修改嵌入UI逻辑,同时必须按添加时间统一遍历排序,不能按类型拆分处理。
当前伪代码:
foreach (ITransactionItem item in transaction.AllItems.OrderBy(x => x.AddedDateTime)) { <text>{item.Description}</text> <text>{item.Price}</text> <div> if (item is TransactionOrder) { // Show order items } else if (item is TransactionGroupOrder) { // Show child orders } else if (item is TransactionCustomItem) { // Show item quantity } <div> }
解决方案(Blazor环境适配)
1. 组件多态拆分(推荐方案)
将不同类型交易项的UI渲染逻辑拆分到独立Blazor组件,父组件仅负责循环渲染基础信息,根据类型匹配对应子组件:
步骤:
- 为每种交易项创建专属子组件:
TransactionOrderComponent.razor:处理订单子项展示TransactionGroupOrderComponent.razor:处理分组订单的子订单展示TransactionCustomItemComponent.razor:处理自定义项的数量展示
- 父组件中循环渲染:
@foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime)) { <div class="transaction-item-container"> <p class="item-description">@item.Description</p> <p class="item-price">@item.Price</p> <div class="item-details"> @if (item is TransactionOrder order) { <TransactionOrderComponent Order="order" /> } else if (item is TransactionGroupOrder groupOrder) { <TransactionGroupOrderComponent GroupOrder="groupOrder" /> } else if (item is TransactionCustomItem customItem) { <TransactionCustomItemComponent CustomItem="customItem" /> } else { <span>未知交易项</span> } </div> </div> }
优势:
- 符合单一职责原则,每种类型的UI逻辑独立维护
- 代码结构清晰,后续新增交易项类型只需添加新组件,无需修改父组件循环逻辑
- Blazor组件的复用性和可维护性更强
2. 访问者模式适配(适配类库不可修改场景)
利用访问者模式将UI渲染逻辑从父组件中剥离,避免类型判断的耦合,同时适配外部类库无法修改的限制:
步骤:
- 定义访问者接口,声明每种交易项的处理方法:
public interface ITransactionItemVisitor { RenderFragment Visit(TransactionOrder order); RenderFragment Visit(TransactionGroupOrder groupOrder); RenderFragment Visit(TransactionCustomItem customItem); }
- 实现UI渲染访问者,封装各类型的渲染逻辑:
public class TransactionItemUiVisitor : ITransactionItemVisitor { public RenderFragment Visit(TransactionOrder order) { return builder => { builder.OpenElement(0, "div"); builder.AddAttribute(1, "class", "order-subitems"); foreach (var subItem in order.Items) { builder.OpenElement(2, "p"); builder.AddContent(3, $"{subItem.Description} - {subItem.Price}"); builder.CloseElement(); } builder.CloseElement(); }; } public RenderFragment Visit(TransactionGroupOrder groupOrder) { return builder => { builder.OpenElement(0, "div"); builder.AddAttribute(1, "class", "group-child-orders"); foreach (var childOrder in groupOrder.ChildOrders) { builder.OpenElement(2, "div"); builder.AddContent(3, childOrder.Description); builder.CloseElement(); } builder.CloseElement(); }; } public RenderFragment Visit(TransactionCustomItem customItem) { return builder => { builder.OpenElement(0, "span"); builder.AddAttribute(1, "class", "custom-item-quantity"); builder.AddContent(2, $"数量: {customItem.Quantity}"); builder.CloseElement(); }; } }
- 父组件中使用访问者:
@{ var uiVisitor = new TransactionItemUiVisitor(); } @foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime)) { <div class="transaction-item-container"> <p>@item.Description</p> <p>@item.Price</p> <div class="item-details"> @RenderItemDetails(item, uiVisitor) </div> </div> } @code { private RenderFragment RenderItemDetails(ITransactionItem item, ITransactionItemVisitor visitor) { return item switch { TransactionOrder order => visitor.Visit(order), TransactionGroupOrder groupOrder => visitor.Visit(groupOrder), TransactionCustomItem customItem => visitor.Visit(customItem), _ => builder => builder.AddContent(0, "未知交易项") }; } }
优势:
- 所有UI渲染逻辑集中到访问者类,父组件无需处理类型判断细节
- 新增交易项类型时,只需扩展访问者接口和实现,符合开闭原则
- 完美适配外部类库无法修改的限制
3. Switch表达式简化条件判断(轻量方案)
如果不想拆分组件或使用访问者,可利用C#的switch表达式替代繁琐的if-else,让代码更简洁:
@foreach (var item in transaction.AllItems.OrderBy(x => x.AddedDateTime)) { <div class="transaction-item-container"> <p>@item.Description</p> <p>@item.Price</p> <div class="item-details"> @(item switch { TransactionOrder order => @<div class="subitems"> @foreach (var sub in order.Items) { <p>@sub.Description - @sub.Price</p> } </div>, TransactionGroupOrder group => @<div class="child-orders"> @foreach (var child in group.ChildOrders) { <div>@child.Description</div> } </div>, TransactionCustomItem custom => @<span>数量: @custom.Quantity</span>, _ => @<span>未知交易项</span> }) </div> </div> }
优势:
- 相比
if-else更简洁直观,代码可读性更高 - 无需额外组件或类,适合交易项类型较少的场景
内容的提问来源于stack exchange,提问作者Conman_123
相关产品推荐
相关产品推荐

