だから私は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>
これは、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();
}
また、このパターンを正しく取得しようとしています。OnInitializedAsync
で非同期サービスを呼び出し、その後、OnAfterRenderAsync
でJavaScriptを呼び出します。重要なのは、サービスから返されるデータに依存するjsを実行するためにjavascript呼び出しが存在することです(目的の動作が有効になるには、データが存在する必要があります)。
何が起こっているのかというと、データが到着する前にjs呼び出しが実行されているということですが、OnInitializedAsync
とOnAfterRenderAsync
のデータ間にこの「依存関係」を実装する方法はありますか?