属性如何导致 Blazor 中的页面更新?

Ste*_*lis 7 blazor blazor-webassembly

我刚刚在 Blazor WebAssembly 中完成了第一个重要的测试应用程序。Blazor 是令人印象深刻的东西,但我发现很难推理属性的更改如何导致 DOM 更新 - 例如,在 Razor 组件中引用该属性。

<div>@SomeProperty</div>
Run Code Online (Sandbox Code Playgroud)
public int SomeProperty {get;set;}
Run Code Online (Sandbox Code Playgroud)

在 WPF 中,很容易推断更改如何流动并导致呈现更改,因为它们是由事件和 DependencyProperty 更改触发的。您可以看到这些并绑定到它们。在 Blazor 中,您可以通过某种方式更改属性值并更新页面。这背后的精确机制有点像魔术。因此,很难推断如何删除复杂组件的不必要更新。

谁能解释一下这个主题的基础?

是否有任何文档或视频深入探讨 Blazor 的这一领域?

MrC*_*tis 10

谁能解释一下这个主题的基础?

要了解更新过程,您需要了解组件。我会尽量保持简短!

所有组件都必须实现IComponent. ComponentBase是一个实现IComponent

public interface IComponent
{
    void Attach(RenderHandle renderHandle);
    Task SetParametersAsync(ParameterView parameters);
}
Run Code Online (Sandbox Code Playgroud)

RenderHandle 的重要部分是:

public readonly struct RenderHandle
{
    public Dispatcher Dispatcher ....
    //....
    public void Render(RenderFragment renderFragment)
    {
        //....
        _renderer.AddToRenderQueue(_componentId, renderFragment);
        //...
    }
}    
Run Code Online (Sandbox Code Playgroud)

RenderFragment 是:

public delegate void RenderFragment(RenderTreeBuilder builder);
Run Code Online (Sandbox Code Playgroud)

ARenderer管理渲染过程。它保存表示为 RenderTree 的 DOM(由浏览器渲染的内容)。当渲染器将组件附加到渲染树时,渲染器会创建RenderHandle并通过调用将其传递给组件Attach。组件使用它RenderHandle与渲染器进行通信。渲染器通过调用 与组件进行通信SetParametersAsync

组件通过调用Render方法RenderHandle并传递RenderFragment委托来“渲染”。

这是一个简单的渲染片段:

protected RenderFragment HelloWorld => (RenderTreeBuilder builder) =>
{
    builder.OpenElement(0, "div");
    builder.AddContent(1, "Hello Blazor");
    builder.CloseElement();
};
Run Code Online (Sandbox Code Playgroud)

调用不会Render渲染组件。它只是将渲染片段放入渲染器队列中。当渲染器运行片段时,它会检查其他组件引用的组件参数更改。它调用其参考参数已更改的任何组件。RenderHandle SetParametersAsync

StateHasChanged是一种ComponentBase方法。 StateHasChanged由 Blazor UI 事件处理程序内部调用,因此您很少需要手动调用它。如果你这样做,问问自己为什么?你的逻辑可能是错误的!它看起来像这样:

var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
    await task;
    StateHasChanged();
}
Run Code Online (Sandbox Code Playgroud)

主要的异常是普通的事件处理程序。如果该事件更新组件中的数据,您需要通过调用 来触发手动更新StateHasChanged。这是标准模式。

private void OnSomethingChanged(object? sender, EventArgs e)
    => this.InvokeAsync(StateHasChanged);
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. StateHasChanged有一种机制可以检测渲染片段是否已排队,因此它不会对多个渲染进行排队。
  2. InvokeAsync确保任务在 UI 线程上运行。它使用Dispatcher上提供的RenderHandle
  3. 当渲染器检查参数更改时,任何对象都被视为脏对象,因为渲染器没有简单的方法来检查相等性。
  4. 渲染器仅在获得线程时间时为其队列提供服务。如果您在按钮单击处理程序中运行一长串同步代码,则在同步代码完成之前不会发生任何事情。

ComponentBase如果您想进一步挖掘,请深入研究-您可以在此处查看代码


Hen*_*man 7

在 Blazor 中,您可以通过某种方式更改属性值并更新页面。

不完全是。房产本身与此无关。例如,您可以使用计时器更新它,并且您不会看到 UI 发生变化。

但通常您会设置该属性以响应 ButtonClick 或其他 Blazor(生命周期)事件。这些都用对 StateHasChanged() 的调用括起来。在 Timer 事件或其他非 Blazor 事件中,您必须自己调用 StateHasChanged()。

StateHasChanged()请求(不是:执行)UI 更新。该更新将在活动结束后或下一次活动期间进行await

因此,很难推断如何删除复杂组件的不必要更新。

这与 不同<div>@SomeProperty</div>

当您拥有<Details ItemId="itemId" />并且itemId是像 int 或字符串这样的基本类型时,只有当 itemId 更改时,才会重新渲染详细信息。

但是,当它是<Details Item="item" />并且 item 是某种复杂类型时,组件将始终与父页面一起重新呈现。

当 Item 没有可变属性时,我有时会在“详细信息”组件中使用以下模式,即 for <Details Item="selectedItem" />

[Parameter]
public Item Item { get; set; } = new();

int oldId = 0;

protected override bool ShouldRender()
{
    if (oldId != Item.Id)
    {
        oldId = Item.Id;
        return true;
    }
    return false;
}
Run Code Online (Sandbox Code Playgroud)