Blazor OnAfterRenderAsync Javascript

KSi*_*Sib 4 c# asp.net-core blazor-client-side

所以我一直在玩 Blazor WebAssembly,但我不知道如何解决这个问题。基本上,我有一个 NavMenu.razor 页面,它将从 JSON 文件异步获取 NavMenuItem 数组。这很好用。现在,我想要做的是向从<NavLink>下面的 foreach 循环中的标签生成的每个锚点添加一个事件侦听器。

该循环之外的 NavLink(异步函数未填充的那个)成功地正确应用了 addSmoothScrollingToNavLinks() 函数。其他 NavLink 没有。似乎它们还没有在 DOM 中。

我猜有一些奇怪的竞争条件,但我不知道如何解决它。关于我如何解决这个问题的任何想法?

导航菜单.razor

@inject HttpClient Http
@inject IJSRuntime jsRuntime

@if (navMenuItems == null)
{
    <div></div>
}
else
{
    @foreach (var navMenuItem in navMenuItems)
    {
        <NavLink class="nav-link" href="@navMenuItem.URL" Match="NavLinkMatch.All">
            <div class="d-flex flex-column align-items-center">
                <div>
                    <i class="@navMenuItem.CssClass fa-2x"></i>
                </div>
                <div class="mt-1">
                    <span>@navMenuItem.Text</span>
                </div>
            </div>
        </NavLink>
    }
}

<NavLink class="nav-link" href="#" Match="NavLinkMatch.All">
    <div class="d-flex flex-column align-items-center">
        This works!
    </div>
</NavLink>

@code {
    private NavMenuItem[] navMenuItems;

    protected override async Task OnInitializedAsync()
    {
        navMenuItems = await Http.GetJsonAsync<NavMenuItem[]>("sample-data/navmenuitems.json");
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        }
    }

    public class NavMenuItem
    {
        public string Text { get; set; }

        public string URL { get; set; }

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

index.html(在 webassembly.js 脚本标签下)

 <script>
        function scrollToTop() {
            window.scroll({
                behavior: 'smooth',
                left: 0,
                top: 0
            });
        }

        window.MyFunctions = {
            addSmoothScrollingToNavLinks: function () {
                let links = document.querySelectorAll("a.nav-link");
                console.log(links);
                // links only has 1 item in it here. The one not generated from the async method
                for (const link of links) {
                    link.addEventListener('click', function (event) {
                        event.preventDefault();
                        scrollToTop();
                    })
                }
            }
        }
    </script>
Run Code Online (Sandbox Code Playgroud)

itm*_*nus 7

那是因为 Blazor不会等待OnInitializedAsync()完成,一旦OnInitializedAsync开始就会开始渲染视图。在 GitHub 上查看源代码

private async Task RunInitAndSetParametersAsync()
{
    OnInitialized();
    var task = OnInitializedAsync();       // NO await here!

    if (task.Status != TaskStatus.RanToCompletion && task.Status != TaskStatus.Canceled)
    {
        // Call state has changed here so that we render after the sync part of OnInitAsync has run
        // and wait for it to finish before we continue. If no async work has been done yet, we want
        // to defer calling StateHasChanged up until the first bit of async code happens or until
        // the end. Additionally, we want to avoid calling StateHasChanged if no
        // async work is to be performed.
        StateHasChanged();                  // Notify here! (happens before await)

        try
        {
            await task;                     // await the OnInitializedAsync to complete!
        }
        ...
Run Code Online (Sandbox Code Playgroud)

如您所见,StateHasChanged()可能在OnInitializedAsync()完成之前开始。由于您在OnInitializedAsync()方法中发送 HTTP 请求,因此组件会在您从sample-data/navmenuitems.json端点获得响应之前呈现。

怎么修

您可以在 Blazor(而不是 js)中绑定事件,并触发由 JavaScript 编写的处理程序。例如,如果您使用的是 ASP.NET Core 3.1.x(不适用于 3.0,请参阅PR#14509):

@foreach (var navMenuItem in navMenuItems)
{
<a class="nav-link" href="@navMenuItem.URL" @onclick="OnClickNavLink" @onclick:preventDefault>
    <div class="d-flex flex-column align-items-center">
        <div>
            <i class="@navMenuItem.CssClass fa-2x"></i>
        </div>
        <div class="mt-1">
            <span>@navMenuItem.Text</span>
        </div>
    </div>
</a>
}

@code{
    ...


    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        //if (firstRender)
        //{
        //    await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        //}
    }
    private async Task OnClickNavLink(MouseEventArgs e)
    {
        Console.WriteLine("click link!");
        await jsRuntime.InvokeVoidAsync("MyFunctions.smoothScrolling");
    }

}
Run Code Online (Sandbox Code Playgroud)

其中MyFunctions.addSmoothScrollingToNavLinks是:

smoothScrolling:function(){
    scrollToTop();
}
Run Code Online (Sandbox Code Playgroud)

  • 我最终通过将scroll-behavior: smooth应用到html并删除onclick事件来让CSS平滑滚动工作。这可能会对其他人有所帮助,所以我会将其标记为答案。欣赏它! (2认同)

小智 6

我也试图让这个模式正确,在OnInitializedAsync那之后调用一些异步服务,在OnAfterRenderAsync. 问题是,javascript 调用是为了执行一些依赖于服务返回的数据的 js(数据必须存在才能使所需的行为生效)。

发生的情况是js调用在数据到达之前运行,有没有办法实现来自 和 的数据之间的这种“依赖关系OnInitializedAsyncOnAfterRenderAsync


bla*_*cat 5

不完美,但适合我,让你等待下载

private bool load = false;
protected override async Task OnInitializedAsync()
{
    navMenuItems = await Http.GetJsonAsync<NavMenuItem[]>("sample-data/navmenuitems.json");
    load = true;
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (load)
    {
        await jsRuntime.InvokeVoidAsync("MyFunctions.addSmoothScrollingToNavLinks");
        load = false;
    }
}
Run Code Online (Sandbox Code Playgroud)