我正在使用一个Razor文件,该文件继承自一个从ComponentBase派生的基。我的理解是,这两个文件通常应该负责处理UI相关的任务。话虽如此,我是否应该将对数据服务的调用放在低级别组件中呢?还是应该将对它们的调用保存在更高级别的组件中,该组件可以编排数据服务,然后简单地将数据传递给组件来处理呈现?(当我提到高或低水平时,我指的是父级组件高级别,孙辈级别低)
据我所知,为处理数据服务而注入的接口将拥有相同的资源(作为一个单例)。因此,我的问题与其说是关于资源的管理,不如说是关于保持稳定。应该在哪里使用数据服务?到处都是还是与世隔绝?谢谢!
发布于 2020-06-20 00:01:50
我将作为将服务隔离到基类的大力支持者。在我得出这一结论之前,我一直遇到的问题是,随着应用程序大小和复杂性的增加,到处传播服务调用变得很混乱。构建每个组件是非常诱人的,因为它是一个原子的东西,它自己处理所有的事情,并得到它的服务注入,但是一旦所有这些组件开始一起组成并且需要开始相互交谈,它就会成为一个巨大的头痛。当您有一些类似于可能涉及任何状态的单例时,这种情况就复杂了,因为组件的底层状态可以很容易地被另一个组件更改。(有时不是故意的--参见EF Core和数据跟踪,以及当跟踪的数据来自两个组件--或者更糟的是,Blazor Server上的两个单独的客户端连接)-在您知道它之前,有太多的地方可以查找错误或者在需要进行更改时进行更改,而跟踪bug就变成了噩梦。
实现组件自治的第二个途径是使用级联参数,但是无论何时,您都要将组件耦合到DOM树上的某个具体组件,而避免耦合才是真正的要点。通常更好的做法是让每个组件表示非常简单的功能,这些功能可以为用户创建更丰富的体验。
因此,正如您在基类中提到的那样,我发现成功的地方是隔离服务,然后尽可能地将每个组件保存在DOM树上,这对我的输出以及查找和修复错误的能力产生了巨大的影响。事实上,我有一个项目,在我开始使用这个方法之前,我不得不放弃两次,现在我正在开发一个功能应用程序,并在一个很好的片段中构建特性。(谢天谢地,这是个业余项目!)
这方面的方法一点也不复杂。在基类中,我将在需要时将方法调用和属性公开为受保护的,并尽可能保持其他所有私有,因此外部可见性是绝对最低的。所有服务调用也发生在基类中,并被封装在私有方法中,从而破坏了服务和UI之间的连接。然后,我将数据作为组件参数传递到DOM树中,并将功能向下传递为EventCallback<T>类型的参数。
以经典的订单列表为例。我可以通过客户ID加载订单列表,然后通过使用表达式体成员筛选主列表来公开已打开的订单和已关闭的订单的列表。所有这些都发生在基类中,但是我设置了它,所以UI唯一可以访问的东西是子列表和方法。在下面的示例中,我通过控制台日志表示服务调用,但您将了解问题中提到如何构建服务调用的基本原理:
OrdersBase.cs
public class OrdersBase : ComponentBase
{
private List<Order> _orders = new List<Order>();
protected List<Order> OpenOrders => _orders.Where(o => o.IsClosed == false).ToList();
protected List<Order> ClosedOrders => _orders.Where(o => o.IsClosed == true).ToList();
protected void CloseOrder(Order order)
{
_orders.Find(o => o.Id == order.Id).IsClosed = true;
Console.WriteLine($"Service was called to close order #{order.Id}");
}
protected void OpenOrder(Order order)
{
_orders.Find(o => o.Id == order.Id).IsClosed = false;
Console.WriteLine($"Service was called to open order #{order.Id}");
}
protected override async Task OnInitializedAsync()
{
Console.WriteLine("Calling service to fill the orders list for customer #1...");
// quick mock up for a few orders
_orders = new List<Order>()
{
new Order() { Id = 1, OrderName = "Order Number 1", CustomerId = 1 },
new Order() { Id = 2, OrderName = "Order Number 2", CustomerId = 1 },
new Order() { Id = 3, OrderName = "Order Number 3", CustomerId = 1 },
new Order() { Id = 4, OrderName = "Order Number 4", CustomerId = 1 },
new Order() { Id = 5, OrderName = "Order Number 5", CustomerId = 1 },
};
Console.WriteLine("Order list filled");
}
}现在,我可以在顶层组件中使用基类,并且只访问受保护的和公共的成员。我可以使用这个高级组件来编排如何安排UI并为委托分发方法,这就是它所要做的。这是非常轻的结果。
Orders.razor
@page "/orders"
@inherits OrdersBase
<div>
<h3>Open Orders:</h3>
<OrdersList Orders="OpenOrders" OnOrderClicked="CloseOrder" />
</div>
<div>
<h3>Closed Orders:</h3>
<OrdersList Orders="ClosedOrders" OnOrderClicked="OpenOrder" />
</div>然后,OrderList组件负责呈现OrderItems列表并传递委托操作。再说一次,只是一个简单的,愚蠢的组成部分。
OrderList.razor
<div>
@foreach (var order in Orders)
{
<OrderItem Order="order" OnOrderClicked="OnOrderClicked.InvokeAsync" />
}
</div>
@code {
[Parameter]
public List<Order> Orders { get; set; }
[Parameter]
public EventCallback<Order> OnOrderClicked { get; set; }
}现在,OrderItem列表可以呈现有关订单的内容,并充当单击目标,当单击订单时,它将一直调用委托,然后返回到基类,这就是方法运行的地方。OrderClicked方法还检查EventCallback,因此如果没有分配委托,则单击不会执行任何操作。
OrderItem.razor
<div @onclick="OrderClicked">
<p>Order Name: @Order.OrderName</p>
</div>
@code {
[Parameter]
public Order Order { get; set; }
[Parameter]
public EventCallback<Order> OnOrderClicked { get; set; }
private void OrderClicked()
{
if(OnOrderClicked.HasDelegate)
{
OnOrderClicked.InvokeAsync(Order);
}
}
}所有这些集合在一起,可以生成显示订单的组件,如果单击打开的订单,它会移动已关闭的订单列表,反之亦然。所有的逻辑都在基类中,每个组件都有一个简单的工作要做,这使得对它的推理更加容易。
这也将为我提供一个指示,说明何时需要将组件分解为更小的组件。我的理念是不要马上向用户展示太多的内容,所以每一页都应该简洁、简单,不需要做太多的事情。为此,当我构建这样的东西时,我可以告诉你,当我的基类或父UI剃须刀文件开始膨胀时,我会走得很远,这会促使将部分功能重构到另一个专用页面。它提供了更多的文件,但也使构建和维护变得更加容易。
事实证明,这是对一个简短问题的长篇回答。你可能同意我的观点,但你可能不同意,但希望它能帮助你决定如何继续前进。
https://stackoverflow.com/questions/62475372
复制相似问题