【问题标题】:How do properties cause pages to update in Blazor?属性如何导致 Blazor 中的页面更新?
【发布时间】:2022-01-31 16:51:14
【问题描述】:

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

<div>@SomeProperty</div>
public int SomeProperty {get;set;}

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

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

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

【问题讨论】:

  • 其他两个好答案的总结:如果你改变了一些东西,你需要用StateHasChanged().来调用刷新 混淆的原因是有时会自动调用StateHasChanged(),给人一种不一致的感觉: “嗯,A B 和 C 刷新了 UI,但 D 没有。为什么?”
  • official documentation 有一个专门的页面进行渲染。
  • 发生的事情完全合乎逻辑。问题在于缺乏知识和理解——这不是一个简单的主题。因此你的问题。官方文档不是很好。我已经“尝试”在下面的回答中简洁地总结了如何以及为什么,但我可能没有成功! Github 上有更长版本的答案。
  • 这两个答案都不太好。谢谢。

标签: blazor blazor-webassembly


【解决方案1】:

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

不完全是。财产本身与此无关。例如,您可以update it with a Timer,您不会看到 UI 发生变化。

但通常您会设置属性以响应 ButtonClick 或其他 Blazor(生命周期)事件。还有那些are bracketed with calls to StateHasChanged()。在 Timer 事件或其他非 Blazor 事件中,您必须自己调用 StateHasChanged()。

Blazor 维护自己的(快速)DOM 副本。在 StateHasChanged() 之后,该副本被重建并与前一个副本进行比较。任何更改都会通过 JavaScript 应用于实际的 DOM。

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

这与&lt;div&gt;@SomeProperty&lt;/div&gt;不同。

当您有&lt;Details ItemId="itemId" /&gt; 并且itemId 是基本类型(如int 或字符串)时,仅当itemId 更改时才会重新呈现详细信息。

但是当它是 &lt;Details Item="item" /&gt; 并且 item 是一些复杂类型时,组件将始终与父页面一起重新呈现。

我有时在 Details 组件中使用以下模式,此时 Item 没有可变属性,即 &lt;Details Item="selectedItem" /&gt;

[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;
}

【讨论】:

  • 很好的解释??
【解决方案2】:

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

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

所有组件都必须实现IComponentComponentBaseIComponent 的实现。

public interface IComponent
{
    void Attach(RenderHandle renderHandle);
    Task SetParametersAsync(ParameterView parameters);
}

RenderHandle 的重要部分是:

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

而一个 RenderFragment 是:

public delegate void RenderFragment(RenderTreeBuilder builder);

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

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

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

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

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

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

var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
    await task;
    StateHasChanged();
}

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

private void OnSomethingChanged(object? sender, EventArgs e)
    => this.InvokeAsync(StateHasChanged);

注意事项:

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

如果您想进一步挖掘,请深入研究ComponentBase - You can view the code here

【讨论】:

  • 这是一个非常有用的深入研究。真的为我澄清了事情。
猜你喜欢
  • 2020-03-02
  • 2019-12-30
  • 1970-01-01
  • 2014-08-11
  • 1970-01-01
  • 2020-09-12
  • 2015-01-14
  • 2013-04-15
  • 1970-01-01
相关资源
最近更新 更多