第一次渲染时 Blazor 组件引用为空

INN*_*VTV 12 c# asp.net-core blazor blazor-client-side

我有一个带有名为 TabChanged 的​​事件操作的自定义组件。在我的 Razor 页面中,我像这样设置了对它的引用:

<TabSet @ref="tabSet">
 ...
</TabSet>

@code {
    private TabSet tabSet;   
    ...
}
Run Code Online (Sandbox Code Playgroud)

OnAfterRenderAsync方法中,我为事件分配了一个处理程序:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if(firstRender)
    {
        tabSet.TabChanged += TabChanged;
    }       
}
Run Code Online (Sandbox Code Playgroud)

页面第一次呈现时,我收到System.NullReferenceException: Object reference not set to an instance of an object错误。

如果我切换到使用后续渲染它工作正常:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if(!firstRender)
    {
        tabSet.TabChanged += TabChanged;
    }       
}
Run Code Online (Sandbox Code Playgroud)

但当然这是草率的,我将在渲染期间触发多个事件处理程序。

如何在第一次渲染时分配参考?我正在按照此处概述的文档进行操作

编辑

这是 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();

}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

和 ITab.cs 接口

using Microsoft.AspNetCore.Components;

namespace PlatformAdmin.Components.Tabs
{
    public interface ITab
    {
        RenderFragment ChildContent { get;  }

        public string Title { get; }
    }
}
Run Code Online (Sandbox Code Playgroud)

它取自此处找到的 Steve Sanderson 示例

编辑 2

这是在第一次渲染时显示 tabSet 为空的调试器:

在此处输入图片说明

并且在附加渲染时不为空:

在此处输入图片说明

INN*_*VTV 9

正如Dani Herrera在评论中指出的那样,这可能是由于组件带有 if/else 语句,而且确实如此。以前,如果对象为空,我会隐藏组件:

@if(Account != null)
{
    <TabSet @ref="tabSet">
     ...
    </TabSet>
}
Run Code Online (Sandbox Code Playgroud)

为简洁起见,我忽略了这一点,并做出了错误的假设,即问题不是有条件的。我非常错误,因为在第一次渲染时对象为空,因此该组件不存在!所以在外面要小心。我通过将我的条件移动到组件中的部分来解决它:

<TabSet @ref="tabSet">
    @if(Account != null)
    {
        <Tab>
         ...
        </Tab>
        <Tab>
         ...
        </Tab>
    }
</TabSet>
Run Code Online (Sandbox Code Playgroud)