在 Blazor 中,为什么调用 NavigationManager.NavigateTo 有时会导致使用旧值进行额外的 OnParametersSetAsync 调用?

In Blazor, why does invoking NavigationManager.NavigateTo sometimes result in an extra OnParametersSetAsync call with old values?

在 Blazor 中,为什么调用 NavigationManager.NavigateTo(string) 有时会导致使用旧值额外调用 OnParametersSetAsync 调用?

我有一个页面通过 CallbackEvent 响应点击,父级调用 NavigationManager.NavigateTo 设置新的 URL,这会导致父级的参数更新,然后组件通过 OnParametersSetAsync 响应新值。 但在此之前,还使用旧值调用了 OnParametersSetAsync——显然是在它们更改之前。似乎我的第二次调用经常在第一次(错误的)调用之前完成,因此第一次调用最后完成,结果很糟糕。

那么,这第一次看似虚假且不正确的对 OnParametersSetAsync 的调用是否只是因为 属性 在事件处理程序等待时可能已更改?我如何确定这是一个虚假电话?

这是一个触发它的例子:

@page "/demo"
@page "/demo/{SelectedOrderId:int}"

@using Microsoft.Extensions.Configuration

@inject IHttpClientFactory clientFactory
@inject IToastService toastService
@inject IConfiguration Configuration
@inject NavigationManager navigationManager

  <div class="mt-4 container-fluid">
    <div class="row">
      <div class="col-12 col-md-8 order-md-2">
        Order @OrderDetail?.OrderId @OrderDetail?.ProductCode
      </div>
      <div class="col-12 col-md-4 order-md-1">
        @foreach (var order in Orders)
        {
          <button @onclick="async () => await OnSelectedOrderIdChanged(order.OrderId)">@order.OrderId</button>        }
      </div>
    </div>

  </div>

@code {
    [CascadingParameter]
    public AppState State { get; set; } = null!;

    public int? CustomerId { get; set; } = 8010;

   [Parameter]
    public int? SelectedOrderId { get; set; } = null;

    private List<Order> Orders { get; set; } = new List<Order>();
    private OrderDetail? OrderDetail { get; set; } = null;


    protected override async Task OnInitializedAsync()
    {
      await LoadOrdersList();
    }

    protected override async Task OnParametersSetAsync()
    {
      Console.WriteLine($"params changed to order {SelectedOrderId}");
      await LoadOrderDetail();
      Console.WriteLine($"done loading order {SelectedOrderId}");

    }

    private async Task LoadOrdersList()
    {
      string serviceEndpoint = Configuration["MyServiceUrl"];
      string url = $"{serviceEndpoint}/orders?customerId={CustomerId}";
      Orders = await clientFactory.CreateClient().GetFromJsonAsync<List<Order>>(url);
    }

    private async Task LoadOrderDetail()
    {
      string serviceEndpoint = Configuration["MyServiceUrl"];
      string url = $"{serviceEndpoint}/orders/{SelectedOrderId}";
      OrderDetail = await clientFactory.CreateClient().GetFromJsonAsync<OrderDetail>(url);
    }


    private async Task OnSelectedOrderIdChanged(int? newOrderId)
    {
      SelectedOrderId = newOrderId;
      await Task.Yield();  // could be anything
      navigationManager.NavigateTo($"/demo/{newOrderId}");
    }
}

当我 运行 然后点击订单 48026528 的其中一个按钮时,我进入了控制台:

params changed to order 48026500

params changed to order 48026528

done loading order 48026528

并且页面显示显示订单48026500!据推测,应该导致的 UI 更新发生得早。那么我如何确定对 48026500 的调用是伪造的(或者它不代表对参数的真正更改)?

我尝试跟踪以前的值并将其与当前值进行比较,但这没有帮助,因为“新”值在之前处理旧值--in换句话说,LoadOrderDetail() 超过 运行s 两次,他们竞争,并且通常首先使用新值 运行s LoadOrderDetail,然后使用旧值的 LoadOrderDetail 完成并覆盖新数据。那么如何避免在这些虚假事件上调用 LoadOrderDetail?

NavigationManager.NavigateTo(string) 仅调用一次。这是您的问题(您非常接近!):

[Parameter]
public int? SelectedOrderId { get; set; } = null;

. . . . .

private async Task OnSelectedOrderIdChanged(int? newOrderId)
{
    // Setting a [Parameter] prop might "work", but is not intended
    SelectedOrderId = newOrderId;  
    await Task.Yield(); 
    // This will also update SelectedOrderId
    navigationManager.NavigateTo($"/demo/{newOrderId}");
}

请注意,[Parameter] 属性应由父项或通过 @page 路由参数设置,绝不能由组件本身设置。

navigationManager.NavigateTo($"/demo/{newOrderId}"); 将设置 SelectedOrderId;这是更新页面的正确方法 [Parameter] 属性! 因此,SelectedOrderId = newOrderId 不是必需的。删除该行,您的代码应按预期工作。


此外,您提到跟踪先前的值以确定何时更新。这可能很有用(尤其是与 ShouldRender 配对时)。最简单的方法是跟踪以前的 id 而不是以前的值。应用于您的代码:

[Parameter]
public int? SelectedOrderId { get; set; }

private int? PreviousOrderId { get; set; }

. . . . .
protected override async Task OnParametersSetAsync()
{
    Console.WriteLine($"params changed to order {SelectedOrderId}");
    if (PreviousOrderId != SelectedOrderId) {
        // Prevent the load from ever happening if the id doesn't change.
        await LoadOrderDetail(); 
    }
    PreviousOrderId = SelectedOrderId;
    Console.WriteLine($"done loading order {SelectedOrderId}");
}