我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?

Posted

技术标签:

【中文标题】我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?【英文标题】:When should I call StateHasChanged and when Blazor automatically intercepts that something is changed? 【发布时间】:2020-05-22 13:30:12 【问题描述】:

我很难理解何时应该调用 StateHasChanged() 以及何时 Blazor 拦截到某些内容已更改,因此必须重新渲染。

我创建了一个示例项目,其中包含一个按钮和一个名为 AddItem 的自定义组件。该组件包含一个带有红色边框的 div 和一个按钮。

我的预期:我希望 AddItem 的 div 在用户单击索引页面中包含的按钮时显示出来。然后我想在用户单击 AddItem 的按钮时隐藏它。

注意: AddItem 不会在外部公开_isVisible 标志,而是包含Show() 方法。所以AddItems.Show()会在索引按钮被点击时被调用。

测试:

    我单击 Index 的单击按钮,然后调用方法 Open()AddItem.Show()。标志_isVisible 设置为true,但没有任何反应,并且调用了Index 的ShouldRender()

    控制台输出:

    渲染索引

    我已将AddItem.Show() 修改为public void Show() _isVisible = true; StateHasChanged();。现在 AddItem 的 div 按预期显示和隐藏。

    控制台输出:

    渲染 AddItem(1° 点击索引按钮) 渲染索引(1° 点击索引按钮) 渲染 AddItem(2° 点击 addItem 的关闭按钮)

    我已经用 <AddItem @ref="AddItem" CloseEventCallback="CallBack" /> 修改了 <AddItem @ref="AddItem" />,从 AddItem 的 Show() 方法中删除了 StateHasChanged。现在 AddItem 的 div 按预期显示和隐藏。

基于测试 3: 如果我将 AddItem 的 CloseEventCallback 设置为任何父母的方法,为什么我不必显式 StateHasChanged?我很难理解它,因为 AddItem 不会在任何地方调用 CloseEventCallback

当 Blazor 了解某些内容已更改,因此必须重新渲染时?

我的示例代码(如果你想试试的话)。

我的索引.razor

<AddItem @ref="AddItem" />
<button @onclick="Open">click</button>
@code 
    AddItem AddItem;

    public void Open()
    
        AddItem.Show();
    

    public void CallBack()
    
    

    protected override bool ShouldRender()
    
        Console.WriteLine("Render INDEX");
        return base.ShouldRender();
    

我的 AddItem 组件

@if (_visible)

    <div style="width: 100px; height: 100px; border: 1px solid red">testo</div>
    <button @onclick="Close">close</button>    


@code 
    private bool _visible = false;

    [Parameter] public EventCallback<bool> CloseEventCallback  get; set; 

    public void Show()
    
        _visible = true;
    

    public void Close()
    
        _visible = false;
    

    protected override bool ShouldRender()
    
        Console.WriteLine("Render ADDITEM");
        return base.ShouldRender();
    

【问题讨论】:

【参考方案1】:

一般来说,StateHasChanged()方法是在UI事件触发后自动调用的, 例如,单击按钮元素后,会引发单击事件,并且 StateHasChanged() 方法是 自动调用以通知组件其状态已更改并且应该重新渲染。

最初访问索引组件时。先渲染父组件,再渲染父组件 渲染它的孩子。

每当单击“打开”按钮时,索引组件都会重新渲染(这是因为 事件的目标是父组件,默认情况下会重新渲染 (无需使用 StateHasChanged)。但不是孩子,他不知道他的 状态发生了变化。为了让孩子意识到他的状态已经改变,并且 应该重新渲染,您应该添加对 StateHasChanged 方法的调用 手动在 Show 方法中。现在,当您单击“打开”按钮时,子组件是 首先重新渲染,然后其父级重新渲染。现在红色 div 呈现可见。

单击“关闭”按钮隐藏红色 div。这次只有子组件重新渲染 (这是因为事件的目标是子组件,默认会重新渲染), 但不是父级。

这种行为是正确的,并且是设计使然。

如果您从 AddItem.Show 方法中删除对 StateHasChanged 方法的调用,请定义此 属性:[Parameter] public EventCallback&lt;bool&gt; CloseEventCallback get; set; ,并添加 父组件中的组件属性为该属性赋值,如下所示: &lt;AddItem @ref="AddItem" CloseEventCallback="CallBack" /&gt;,你会注意到外表没有变化, 但是这次单击“打开”按钮时重新渲染的顺序是首先是父级 重新渲染,然后孩子重新渲染。这准确描述了您发现表达的问题 在您来自 cmets 的问题中:

那么,为什么即使 CloseEventCallback 没有在任何地方调用,我的测试 3 也会按预期工作?

你是对的......在进行进一步调查之前,我无法真正解释这种行为。 我会试着找出发生了什么,并让你知道。

AddItem 的 close 方法调用 CloseEventCallback 来通知父级它应该重新渲染。

注意:您的代码使用布尔类型说明符定义 CloseEventCallback,因此您必须定义一个方法 在具有布尔参数的父组件中。当您调用 CloseEventCallback 'delegate' 你实际上调用了 Index.Callback 方法,你应该给它传递一个布尔值。当然,如果你通过 组件的值,您希望它重新呈现,以便可以在 UI 中看到新状态。这是 EventCallback 提供的功能:虽然事件是在子组件中触发的, 它的目标是父组件,这会导致父组件重新渲染。

我想知道如果调用了订阅的 EventCallback 之一,为什么父组件应该重新呈现自己?

这正是我在上一段中试图解释的内容。 EventCallback 类型是专门为解决事件目标的问题而设计的,将事件路由到组件 其状态已更改(父组件),并重新渲染它。

【讨论】:

谢谢Enet,现在我更清楚了。我的脑海里还有两个问题。 1. 正如 Blazor Docs 所预期的那样,AddItem 的 close 方法调用 CloseEventCallback 来通知父级它应该重新渲染。那么,即使没有在任何地方调用 CloseEventCallback,为什么我的测试 3 也能按预期工作? 2.我想知道如果调用了其中一个订阅的EventCallback,为什么父组件应该重新渲染自己?我可能有一个与 UI 状态不严格相关的 EventCallback。我知道这是一个奇怪的问题,我只是问,因为我想了解 @Leonardo Lurci ,我已经更新了我的答案,以便在上面的评论中提供您的问题。这是一个非常复杂的主题...请不要犹豫,提出问题。 谢谢。我不知道渲染过程中有特定的顺序。当您写 When you invoke the CloseEventCallback [...] you expect it to re-render 时,我同意您的看法,确实我是一个奇怪的问题,只是为了了解 Blazor 引擎下发生了什么。如果可能,我会问你,你在哪里找到这些信息:在 GitHub 上寻找 asp.net Core Blazor?我正在尝试学习 Blazor,我想了解的不仅仅是“如何使用 Blazor / 你可以用 Blazor 做什么”。你有什么建议吗?如果您在我的测试 3 中发现任何内容,请告诉我。 我的学习来源是不久前很差的文档,***中的回答问题,以及github中blazor的issues部分。我不访问其他来源,例如博客,也不会在第三方组件上浪费时间。我完全专注于 Blazor 组件模型以及如何使用它。我还从 Blazor 团队创建的代码示例中学到了很多东西,例如 FlightFinder、Blazing Pizza 等。 最好的材料是由史蒂夫安德森提供的......只要去他的存储库,阅读他的样本,分析它们,运行它们等等。这是 rynowak 提供的问题的链接,其中之一Blazor 团队,关于 EventCallback。确保您阅读了所有内容:github.com/dotnet/aspnetcore/issues/6351【参考方案2】:

组件必须在首次添加到组件时呈现 父组件的层次结构。这是唯一一次 组件必须渲染。组件可能会在其他时间渲染 符合他们自己的逻辑和约定。

SPA 应用程序遵循以下组件化架构:

将该树中的每个节点视为我们在 blazor 中的一个组件。拥有组件父母和他们的孩子。

StateHasChanged 由 ComponentBase(任何 blazor 组件的标准类)接收。这是包含在以下时间自动触发重新渲染的逻辑:

从父组件应用一组更新的参数后。 应用级联参数的更新值后。之后 事件通知并调用其自己的事件处理程序之一。 在调用自己的 StateHasChanged 方法后(请参阅 ASP.NET Core Razor 组件生命周期)。

与 blazor 的自动渲染调用不同,我们使用 StateHasChanged。这是当框架本身在上述时间之一渲染时,它只渲染它的子组件(子组件)。当我们调用 StateHasChanged。大多数从 ComponentBase 继承的组件都会被渲染,无论它们在树中的位置如何。 (不必要的重新渲染)。因此,我们应该避免使用 StateHasChanged,让框架处理渲染。

调用 StateHasChanged 有意义的场合:

异步处理程序涉及多个异步阶段 接收来自 Blazor 渲染外部的调用和 事件处理系统 在由 a 重新渲染的子树之外渲染组件 特定事件

了解更多:https://docs.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-5.0

【讨论】:

以上是关于我应该何时调用 StateHasChanged 以及 Blazor 何时自动拦截某些更改?的主要内容,如果未能解决你的问题,请参考以下文章

Blazor 中的 StateHasChanged() 与 InvokeAsync(StateHasChanged)

为 HTML 控件提供建议:我应该何时调用 DispEventUnadvise?

无法让 StateHasChanged 更新 @if 语句

我应该何时调用 CancellationToken.ThrowIfCancellationRequested?

Unity何时调用Inspector初始化

编码自定义 SplitViewController - 我应该何时调用 viewWillAppear、viewDidAppear 等...?