首次渲染时 Blazor 组件引用为空
Blazor Component Reference Null on First Render
我有一个带有名为 TabChanged 的事件操作的自定义组件。在我的 Razor 页面中,我像这样设置对它的引用:
<TabSet @ref="tabSet">
...
</TabSet>
@code {
private TabSet tabSet;
...
}
在 OnAfterRenderAsync 方法中,我为事件分配了一个处理程序:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
tabSet.TabChanged += TabChanged;
}
}
第一次呈现页面时,我收到 System.NullReferenceException: Object reference not set to an instance of an object 错误。
如果我切换到使用后续渲染它工作正常:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(!firstRender)
{
tabSet.TabChanged += TabChanged;
}
}
但这当然很草率,我将在渲染期间触发多个事件处理程序。
如何在第一次渲染时一次性分配参考?我正在按照 here
概述的文档进行操作
编辑
这是 TabSet.razor 文件:
@using Components.Tabs
<!-- Display the tab headers -->
<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>
<!-- Display body for only the active tab -->
<div class="nav-tabs-body" style="padding:15px; padding-top:30px;">
@ActiveTab?.ChildContent
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public ITab ActiveTab { get; private set; }
public event Action TabChanged;
public void AddTab(ITab tab)
{
if (ActiveTab == null)
{
SetActiveTab(tab);
}
}
public void RemoveTab(ITab tab)
{
if (ActiveTab == tab)
{
SetActiveTab(null);
}
}
public void SetActiveTab(ITab tab)
{
if (ActiveTab != tab)
{
ActiveTab = tab;
NotifyStateChanged();
StateHasChanged();
}
}
private void NotifyStateChanged() => TabChanged?.Invoke();
}
TabSet 还使用 Tab.razor:
@using Components.Tabs
@implements ITab
<li>
<a @onclick="Activate" class="nav-link @TitleCssClass" role="button">
@Title
</a>
</li>
@code {
[CascadingParameter]
public TabSet ContainerTabSet { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private string TitleCssClass => ContainerTabSet.ActiveTab == this ? "active" : null;
protected override void OnInitialized()
{
ContainerTabSet.AddTab(this);
}
private void Activate()
{
ContainerTabSet.SetActiveTab(this);
}
}
和ITab.cs接口
using Microsoft.AspNetCore.Components;
namespace PlatformAdmin.Components.Tabs
{
public interface ITab
{
RenderFragment ChildContent { get; }
public string Title { get; }
}
}
它取自发现的史蒂夫·桑德森 (Steve Sanderson) 示例 here
编辑 2
这是调试器,在第一次渲染时显示 tabSet 为空:
并且在其他渲染中不为空:
在第一次渲染中,组件被渲染。第一次渲染后,引用对象是指组件。因此,第一次,组件的 ref 为 null(不是 ref)。
有关更多详细信息,blazor 的文档是 here
中的详细问题
tabSet 变量仅在呈现组件且其输出包含 TabSet 元素后填充。在那之前,没有什么可以参考的。要在组件完成呈现后操作组件引用,请使用 OnAfterRenderAsync 或 OnAfterRender 方法。
正如 Dani Herrera 在评论中指出的那样,这可能是由于组件带有 if/else 语句,而且确实如此。以前,如果对象为空,我会隐藏组件:
@if(Account != null)
{
<TabSet @ref="tabSet">
...
</TabSet>
}
为了简洁起见,我忽略了这一点,并错误地假设问题不是有条件的。我错了,因为在第一次渲染时对象为空,因此组件不存在!所以在外面要小心。我通过将我的条件移动到组件中的部分来解决它:
<TabSet @ref="tabSet">
@if(Account != null)
{
<Tab>
...
</Tab>
<Tab>
...
</Tab>
}
</TabSet>
在我的例子中,它是通过初始化对象解决的
EX: private TabSet tabSet = new TabSet();
我有一个带有名为 TabChanged 的事件操作的自定义组件。在我的 Razor 页面中,我像这样设置对它的引用:
<TabSet @ref="tabSet">
...
</TabSet>
@code {
private TabSet tabSet;
...
}
在 OnAfterRenderAsync 方法中,我为事件分配了一个处理程序:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(firstRender)
{
tabSet.TabChanged += TabChanged;
}
}
第一次呈现页面时,我收到 System.NullReferenceException: Object reference not set to an instance of an object 错误。
如果我切换到使用后续渲染它工作正常:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if(!firstRender)
{
tabSet.TabChanged += TabChanged;
}
}
但这当然很草率,我将在渲染期间触发多个事件处理程序。
如何在第一次渲染时一次性分配参考?我正在按照 here
概述的文档进行操作编辑
这是 TabSet.razor 文件:
@using Components.Tabs
<!-- Display the tab headers -->
<CascadingValue Value="this">
<ul class="nav nav-tabs">
@ChildContent
</ul>
</CascadingValue>
<!-- Display body for only the active tab -->
<div class="nav-tabs-body" style="padding:15px; padding-top:30px;">
@ActiveTab?.ChildContent
</div>
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
public ITab ActiveTab { get; private set; }
public event Action TabChanged;
public void AddTab(ITab tab)
{
if (ActiveTab == null)
{
SetActiveTab(tab);
}
}
public void RemoveTab(ITab tab)
{
if (ActiveTab == tab)
{
SetActiveTab(null);
}
}
public void SetActiveTab(ITab tab)
{
if (ActiveTab != tab)
{
ActiveTab = tab;
NotifyStateChanged();
StateHasChanged();
}
}
private void NotifyStateChanged() => TabChanged?.Invoke();
}
TabSet 还使用 Tab.razor:
@using Components.Tabs
@implements ITab
<li>
<a @onclick="Activate" class="nav-link @TitleCssClass" role="button">
@Title
</a>
</li>
@code {
[CascadingParameter]
public TabSet ContainerTabSet { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private string TitleCssClass => ContainerTabSet.ActiveTab == this ? "active" : null;
protected override void OnInitialized()
{
ContainerTabSet.AddTab(this);
}
private void Activate()
{
ContainerTabSet.SetActiveTab(this);
}
}
和ITab.cs接口
using Microsoft.AspNetCore.Components;
namespace PlatformAdmin.Components.Tabs
{
public interface ITab
{
RenderFragment ChildContent { get; }
public string Title { get; }
}
}
它取自发现的史蒂夫·桑德森 (Steve Sanderson) 示例 here
编辑 2
这是调试器,在第一次渲染时显示 tabSet 为空:
并且在其他渲染中不为空:
在第一次渲染中,组件被渲染。第一次渲染后,引用对象是指组件。因此,第一次,组件的 ref 为 null(不是 ref)。 有关更多详细信息,blazor 的文档是 here
中的详细问题tabSet 变量仅在呈现组件且其输出包含 TabSet 元素后填充。在那之前,没有什么可以参考的。要在组件完成呈现后操作组件引用,请使用 OnAfterRenderAsync 或 OnAfterRender 方法。
正如 Dani Herrera 在评论中指出的那样,这可能是由于组件带有 if/else 语句,而且确实如此。以前,如果对象为空,我会隐藏组件:
@if(Account != null)
{
<TabSet @ref="tabSet">
...
</TabSet>
}
为了简洁起见,我忽略了这一点,并错误地假设问题不是有条件的。我错了,因为在第一次渲染时对象为空,因此组件不存在!所以在外面要小心。我通过将我的条件移动到组件中的部分来解决它:
<TabSet @ref="tabSet">
@if(Account != null)
{
<Tab>
...
</Tab>
<Tab>
...
</Tab>
}
</TabSet>
在我的例子中,它是通过初始化对象解决的
EX: private TabSet tabSet = new TabSet();