我应该何时调用 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<bool> CloseEventCallback get; set;
,并添加
父组件中的组件属性为该属性赋值,如下所示:
<AddItem @ref="AddItem" CloseEventCallback="CallBack" />
,你会注意到外表没有变化,
但是这次单击“打开”按钮时重新渲染的顺序是首先是父级
重新渲染,然后孩子重新渲染。这准确描述了您发现表达的问题
在您来自 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?
我应该何时调用 CancellationToken.ThrowIfCancellationRequested?
编码自定义 SplitViewController - 我应该何时调用 viewWillAppear、viewDidAppear 等...?