首次渲染时 Blazor 组件引用为空

Posted

技术标签:

【中文标题】首次渲染时 Blazor 组件引用为空【英文标题】:Blazor Component Reference Null on First Render 【发布时间】:2020-03-08 18:21:26 【问题描述】:

我有一个自定义组件,其中包含一个名为 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 为 null 的调试器:

并且在其他渲染中不为空:

【问题讨论】:

我试过你的代码,但是在使用if(firstRender) tabSet.TabChanged += TabChanged; 时我没有得到一个空异常。 TabChanged 是代表吗?你能告诉我们一种重现的方法吗? 感谢您测试 itminus。我更新了我的问题以包含 TabSet 组件文件。它取自史蒂夫·桑德森 (Steve Sanderson) 的示例:gist.github.com/SteveSandersonMS/… 但我添加了该事件。 我使用您的代码创建了一个 blazor 客户端项目和一个 blazor 服务器端项目。但是,它对我来说效果很好。见screenshot。我不知道缺少什么? 也不确定。我再次运行它并使用在第一次和其他渲染期间显示调试器的屏幕截图更新了我的问题。 tabSet 在第一次渲染后为 null,但在下一次渲染时不为 null! 你的&lt;TabSet @ref="tabSet"&gt;在里面和if (吗? 【参考方案1】:

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

@if(Account != null)

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

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

<TabSet @ref="tabSet">
    @if(Account != null)
    
        <Tab>
         ...
        </Tab>
        <Tab>
         ...
        </Tab>
    
</TabSet>

【讨论】:

【参考方案2】:

在第一次渲染中,组件被渲染。第一次渲染后,引用对象是引用组件。因此,第一次,组件的 ref 为空(不是 ref)。 更多详细信息,blazor 的文档是here 中的详细问题

tabSet 变量仅在组件被渲染并且其输出包含 TabSet 元素后才被填充。在那之前,没有什么可以参考的。要在组件完成渲染后操作组件引用,请使用 OnAfterRenderAsync 或 OnAfterRender 方法。

【讨论】:

也许你是对的,但文件似乎声明引用应该可用 OnAfterRender 或 OnAfterRenderAsync ,因为我有它并且没有说明如果FirstRender 为真,它们将不可用。我会再挖一点看看。 OnAfterRender 被调用了两次。【参考方案3】:

在我的情况下,它是通过初始化对象来解决的

EX: private TabSet tabSet = new TabSet();

【讨论】:

这对我不起作用,我无法想象它会正确的情况。这只是避免了异常,但创建了一个组件实例,它不一定(我假设实际上从未)在初始化前者时引用包含 ref 的组件。在这个 ref 上执行的方法实际上可能不会命中渲染的组件实例。

以上是关于首次渲染时 Blazor 组件引用为空的主要内容,如果未能解决你的问题,请参考以下文章

将 Blazor 组件渲染到现有 div(Blazor 无权访问)

从 JavaScript 渲染 Blazor 组件 - 如何在组件渲染后从 JavaScript 更新 Blazor 组件参数

Blazor 重新渲染组件块元素 [关闭]

使用参数渲染 Blazor 子组件

如何从 Blazor 中的另一个组件重新渲染一个组件?

混合 Blazor 组件、DI 和 C# 8 可为空