web-dev-qa-db-ja.com

Blazor OnAfterRenderAsync Javascript

だから私はBlazorWebAssemblyで遊んでいて、この問題を解決する方法がわかりません。基本的に、JSONファイルから非同期でNavMenuItemの配列を取得するNavMenu.razorページがあります。それはうまくいきます。ここで、私がやろうとしているのは、以下のforeachループの<NavLink>タグから生成される各アンカーにイベントリスナーを追加することです。

そのループの外側のNavLink(async関数によって設定されていないもの)には、addSmoothScrollingToNavLinks()関数が正しく適用されています。他のNavLinkはそうではありません。まだDOMに含まれていないようです。

奇妙な競合状態があると思いますが、それを解決する方法がわかりません。これを修正する方法について何かアイデアはありますか?

NavMenu.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; }
    }
}

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>
2
KSib

これは、Blazorが[〜#〜] not [〜#〜]OnInitializedAsync()が完了するのを待って、レンダリングを開始するためです。 OnInitializedAsyncが開始されたら表示します。 GitHubのソースコード を参照してください:

 private async Task RunInitAndSetParametersAsync()
 {
 OnInitialized(); 
 var task = OnInitializedAsync();  ここで待つ必要はありません!
 
 if(task.Status!= TaskStatus.RanToCompletion && task.Status!= TaskStatus.Canceled)
 {
 //ここで呼び出し状態が変更されたためOnInitAsyncの同期部分が実行された後にレンダリングします
 //続行する前にそれが終了するのを待ちます。非同期作業がまだ行われていない場合は、
 //非同期コードの最初のビットが発生するまでまたは
 //終了するまでStateHasChangedの呼び出しを延期します。さらに、no 
 //非同期作業を実行する場合はStateHasChangedを呼び出さないようにします。
 StateHasChanged(); ここに通知してください! (待つ前に起こります)
 
 try 
 {
タスクを待つ; onInitializedAsyncが完了するのを待ちます!
} 
 ... 

ご覧のとおり、StateHasChanged()が完了する前にOnInitializedAsync()が開始される場合があります。 OnInitializedAsync()メソッド内でHTTPリクエストを送信するため、sample-data/navmenuitems.jsonエンドポイントから応答を取得する前にコンポーネントがレンダリングされます。

直し方

(jsの代わりに)Blazorでイベントをバインドし、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");
    }

}

ここで、MyFunctions.addSmoothScrollingToNavLinksは次のとおりです。

smoothScrolling:function(){
    scrollToTop();
}
3
itminus

また、このパターンを正しく取得しようとしています。OnInitializedAsyncで非同期サービスを呼び出し、その後、OnAfterRenderAsyncでJavaScriptを呼び出します。重要なのは、サービスから返されるデータに依存するjsを実行するためにjavascript呼び出しが存在することです(目的の動作が有効になるには、データが存在する必要があります)。

何が起こっているのかというと、データが到着する前にjs呼び出しが実行されているということですが、OnInitializedAsyncOnAfterRenderAsyncのデータ間にこの「依存関係」を実装する方法はありますか?

1
Luís Antunes