blazor 组件何时渲染?

inn*_*227 7 rendering async-await blazor blazor-webassembly

在此页面的下图中,它显示当调用 OnInitializedAsync 返回未完成的任务时,它将等待该任务,然后渲染组件。

然而,当返回未完成的任务时,实际发生的情况似乎是立即渲染组件,然后在未完成的任务完成后再次渲染它。

在此输入图像描述

该页面后面的一个示例似乎证实了这一点。如果组件在调用 OnInitializedAsync 之后没有立即呈现,而是仅在返回的任务完成后第一次呈现,您将永远不会看到“正在加载...”消息。

OnParametersSetAsync 行为看起来相同。当返回未完成的任务时,它立即渲染一次,然后在该任务完成后再次渲染。

我是否误解了渲染生命周期,或者这是文档中的错误?

谢谢

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}
Run Code Online (Sandbox Code Playgroud)

Hen*_*man 7

简短的摘要

  • 从概念上讲,Blazor 添加了两个“免费”StateHasChanged 调用,一个在每个生命周期事件和 UI 事件之前,一个在每个生命周期事件和 UI 事件之后。
  • StateHasChanged 仅请求 html 更新,但不执行更新。
  • 更新请求只能在事件发生后
    或主线程被释放时满足await
    • 不是但每个await都会释放线程。

因此,当您想确保屏幕更新时,请使用

StateHasChanged();
await Task.Delay(1);  // Task.Yield() does not always work
Run Code Online (Sandbox Code Playgroud)

旧答案

当返回未完成的任务时,它会立即渲染组件,然后在未完成的任务完成后再次渲染它。

是的,这是一个可能的顺序。

该流程图显示了显示组件的步骤。从图片中不太清楚的是,实际渲染不是此流程的一部分,它在同步上下文上异步发生。await当你的代码有问题时,就会发生这种情况。

所以我们有这个基础的非异步序列:

  • 初始化时[异步]

  • OnParametersSet[异步]

  • 使成为

  • OnAfterRender[异步]

但是,当此代码路径中存在异步内容时,在await. 当您在此流程中调用 StateHasChanged 时,可以进行更多渲染。


MrC*_*tis 4

为了完全回答您的问题,我们需要深入研究代码ComponentBase

您的代码在异步世界中运行,其中代码块可以产生并将控制权交还给调用者 - 您的“未完成的任务已返回”。

SetParametersAsync当组件首次渲染以及任何参数发生更改时,由渲染器调用。

public virtual Task SetParametersAsync(ParameterView parameters)
{
    parameters.SetParameterProperties(this);
    if (!_initialized)
    {
        _initialized = true;
        return RunInitAndSetParametersAsync();
    }
    else
        return CallOnParametersSetAsync();
}
Run Code Online (Sandbox Code Playgroud)

RunInitAndSetParametersAsync负责初始化。我留下了 MS 编码员的评论,其中解释了这些StateHasChanged调用。

private async Task RunInitAndSetParametersAsync()
{
    OnInitialized();
    var task = OnInitializedAsync();

    if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
    {
        // Call state has changed here so that we render after the sync part of OnInitAsync has run
        // and wait for it to finish before we continue. If no async work has been done yet, we want
        // to defer calling StateHasChanged up until the first bit of async code happens or until
        // the end. Additionally, we want to avoid calling StateHasChanged if no
        // async work is to be performed.
        StateHasChanged();
        try
        {
            await task;
        }
        catch // avoiding exception filters for AOT runtime support
        {
            if (!task.IsCanceled)
                throw;
        }
        // Don't call StateHasChanged here. CallOnParametersSetAsync should handle that for us.
    }
    await CallOnParametersSetAsync();
}
Run Code Online (Sandbox Code Playgroud)

CallOnParametersSetAsync每次参数更改时都会调用。

private Task CallOnParametersSetAsync()
{
    OnParametersSet();
    var task = OnParametersSetAsync();
    // If no async work is to be performed, i.e. the task has already ran to completion
    // or was canceled by the time we got to inspect it, avoid going async and re-invoking
    // StateHasChanged at the culmination of the async work.
    var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
        task.Status != TaskStatus.Canceled;

    // We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and
    // the synchronous part of OnParametersSetAsync has run.
    StateHasChanged();

    return shouldAwaitTask ?
        CallStateHasChangedOnAsyncCompletion(task) :
        Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)

在图中替换StateHasChanged上面代码中的“渲染”。

该图使用了“Render”这个作品,这有点误导。这意味着 UI 会重新渲染,而实际发生的情况是渲染片段(为组件构建 UI 标记的代码块)在渲染器的渲染队列中排队。它应该说“请求渲染”或类似的内容。

如果触发渲染事件或调用 的组件代码StateHasChanged都是同步代码,则渲染器仅在代码完成时获取线程时间。代码块需要“让出”以便渲染器在此过程中获得线程时间。

同样重要的是要了解并非所有基于任务的方法都会产生结果。许多只是任务包装器中的同步代码。

因此,如果代码在OnInitializedAsyncOnParametersSetAsync中产生,则在第一次产生时会产生渲染事件,然后在完成时产生渲染事件。

在同步代码块中“产生”的常见做法是在您希望渲染器渲染的位置添加这行代码。

await Task.Delay(1);
Run Code Online (Sandbox Code Playgroud)

您可以ComponentBase在这里看到 - https://github.com/dotnet/aspnetcore/blob/main/src/Components/Components/src/ComponentBase.cs