You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor中OnInitializedAsync用await时OnAfterRenderAsync提前触发致JS报错

Blazor异步初始化后调用JS元素为空的问题解决思路

在Blazor Server页面中,重写OnAfterRenderAsync调用JavaScript时遇到异常:当OnInitializedAsync中使用带await的异步操作(比如示例中的await Task.Delay(1000)),会抛出以下错误:

blazor.server.js:1 [2023-09-14T15:14:51.292Z] Error: Microsoft.JSInterop.JSException: Cannot read properties of null (reading 'value')

若去掉异步等待逻辑,调用非异步版本则正常,且全程已正确使用await。即使在代码中添加了Initialised和Rendered状态检查,确保两个生命周期方法都完成后再执行JS,问题依然存在。

复现示例代码

可替换默认模板的FetchData.razor页面测试:

@page "/fetchdata"
@inject IJSRuntime JS

@LogSteps
<br />

@if (tmpExample == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@tmpExample">
        <InputNumber id="IntMyProperty" @bind-Value="tmpExample.IntMyProperty" />
        <InputText id="StringProperty" @bind-Value="tmpExample.StringProperty" />
    </EditForm>
}

@code {
    private bool Initialised = false;
    private bool Rendered = false;

    protected override async Task OnInitializedAsync()
    {
        LogStep("OnInitializedAsync-Start;");

        await Task.Delay(1000); // 注释掉该行就能正常运行!

        tmpExample = new()
            {
                IntMyProperty = 1,
                StringProperty = "hello"
            };

        LogStep("OnInitializedAsync-End;");
        Initialised = true;
        await InvokeJavaScript();
    }

    public string LogSteps = "";
    void LogStep(string step)
    {
        LogSteps += step;
        Console.WriteLine(step);
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            LogStep("OnAfterRenderAsync-FirstRender;");
            Rendered = true;
            await InvokeJavaScript();
        }
        else
        {
            LogStep("OnAfterRenderAsync-OtherRender;");
        }
    }

    internal Example tmpExample;
    internal class Example
    {
        public int IntMyProperty { get; set; }
        public string StringProperty { get; set; }
    }

    async Task InvokeJavaScript()
    {
        LogStep("InvokeJavaScript-1-Initialised=" + Initialised.ToString() + "; Rendered=" + Rendered.ToString() + ";");

        if (Initialised && Rendered)
        {
            LogStep("InvokeJavaScript-StartingRun;");
            
            await JS.InvokeVoidAsync("eval", "alert(document.getElementById('StringProperty').value)");
            
            LogStep("InvokeJavaScript-FinishedRun;");
        }
    }
}

问题核心原因

当OnInitializedAsync存在异步延迟时,Blazor的渲染流程会先完成第一次OnAfterRenderAsync(此时tmpExample为null,页面仅显示Loading,表单元素尚未渲染)。之后OnInitializedAsync完成,设置tmpExample并触发重新渲染,但这次渲染的OnAfterRenderAsync中firstRender为false,不会更新Rendered状态。此时虽然Initialised和Rendered都为true,但实际表单元素是在第二次渲染才生成的,第一次渲染的DOM中并没有目标元素,JS调用基于的是旧DOM状态,因此报错。

解决思路与方案

方案1:调整JS调用时机到元素真正渲染完成后

移除Rendered状态变量,改为在OnAfterRenderAsync中检查tmpExample是否已初始化,确保表单元素已渲染后再执行JS,同时移除OnInitializedAsync中的JS调用:

protected override async Task OnInitializedAsync()
{
    LogStep("OnInitializedAsync-Start;");

    await Task.Delay(1000);

    tmpExample = new()
        {
            IntMyProperty = 1,
            StringProperty = "hello"
        };

    LogStep("OnInitializedAsync-End;");
    Initialised = true;
    // 移除这里的InvokeJavaScript调用,此时DOM还未更新
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    // 只要tmpExample存在,说明表单元素已渲染
    if (tmpExample != null)
    {
        LogStep("OnAfterRenderAsync-TryingJS;");
        await JS.InvokeVoidAsync("eval", "alert(document.getElementById('StringProperty').value)");
    }
}

方案2:使用ElementReference代替ID选择器(推荐)

Blazor官方推荐使用ElementReference直接引用DOM元素,避免ID选择器的不确定性:

  1. 修改Razor部分的InputText:
<InputText @ref="stringInputRef" @bind-Value="tmpExample.StringProperty" />
  1. 在@code块中定义元素引用:
private ElementReference stringInputRef;
  1. 在OnAfterRenderAsync中调用JS时使用该引用:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (tmpExample != null)
    {
        // 调用自定义JS函数,传入元素引用
        await JS.InvokeVoidAsync("alertElementValue", stringInputRef);
    }
}
  1. 在_Host.cshtml中添加对应的JS函数:
window.alertElementValue = function(element) {
    alert(element.value);
}

方案3:优化状态检查与重试逻辑

如果需要保留原有状态检查逻辑,可修改Rendered更新时机,并在每次渲染时尝试执行JS,同时检查元素是否存在:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        LogStep("OnAfterRenderAsync-FirstRender;");
        Rendered = true;
    }
    else
    {
        LogStep("OnAfterRenderAsync-OtherRender;");
    }
    // 每次渲染都尝试执行JS
    await InvokeJavaScript();
}

async Task InvokeJavaScript()
{
    LogStep("InvokeJavaScript-1-Initialised=" + Initialised.ToString() + "; Rendered=" + Rendered.ToString() + ";");

    if (Initialised && Rendered)
    {
        // 先检查元素是否存在
        var elementExists = await JS.InvokeAsync<bool>("eval", "document.getElementById('StringProperty') !== null");
        if (elementExists)
        {
            LogStep("InvokeJavaScript-StartingRun;");
            await JS.InvokeVoidAsync("eval", "alert(document.getElementById('StringProperty').value)");
            LogStep("InvokeJavaScript-FinishedRun;");
        }
        else
        {
            LogStep("InvokeJavaScript-ElementNotFound;");
        }
    }
}

内容的提问来源于stack exchange,提问作者Jay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 00:23:13