Blazor OnAfterRenderAsync Javascript
Blazor OnAfterRenderAsync Javascript
所以我一直在研究 Blazor WebAssembly,但我不知道如何解决这个问题。基本上,我有一个 NavMenu.razor 页面,它将从 JSON 文件异步获取 NavMenuItem 数组。那很好用。现在,我要做的是为每个锚点添加一个事件监听器,这些锚点是从下面的 foreach 循环中的 <NavLink>
标签生成的。
该循环之外的 NavLink(未由异步函数填充的那个)成功地正确应用了 addSmoothScrollingToNavLinks() 函数。其他 NavLinks 没有。好像他们还没有在 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 不会 等待 OnInitializedAsync()
完成,并且会在 OnInitializedAsync
启动后开始渲染视图。见 source code on 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!
}
...
如您所见,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");
}
}
其中 MyFunctions.addSmoothScrollingToNavLinks
是:
smoothScrolling:function(){
scrollToTop();
}
我也在努力使这种模式正确,在 OnInitializedAsync
中调用一些异步服务,然后在 OnAfterRenderAsync
中调用一些 javascript。问题是 javascript 调用是用来执行一些 js 的,这些 js 取决于服务返回的数据(数据必须在那里才能使所需的行为生效)。
发生的事情是 js 调用是 运行 在数据到达之前,有没有办法在来自 OnInitializedAsync
和 OnAfterRenderAsync
的数据之间实现这个 "dependency" ?
不完美,但对我有用,
让你等待下载
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;
}
}
所以我一直在研究 Blazor WebAssembly,但我不知道如何解决这个问题。基本上,我有一个 NavMenu.razor 页面,它将从 JSON 文件异步获取 NavMenuItem 数组。那很好用。现在,我要做的是为每个锚点添加一个事件监听器,这些锚点是从下面的 foreach 循环中的 <NavLink>
标签生成的。
该循环之外的 NavLink(未由异步函数填充的那个)成功地正确应用了 addSmoothScrollingToNavLinks() 函数。其他 NavLinks 没有。好像他们还没有在 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 不会 等待 OnInitializedAsync()
完成,并且会在 OnInitializedAsync
启动后开始渲染视图。见 source code on 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!
}
...
如您所见,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");
}
}
其中 MyFunctions.addSmoothScrollingToNavLinks
是:
smoothScrolling:function(){
scrollToTop();
}
我也在努力使这种模式正确,在 OnInitializedAsync
中调用一些异步服务,然后在 OnAfterRenderAsync
中调用一些 javascript。问题是 javascript 调用是用来执行一些 js 的,这些 js 取决于服务返回的数据(数据必须在那里才能使所需的行为生效)。
发生的事情是 js 调用是 运行 在数据到达之前,有没有办法在来自 OnInitializedAsync
和 OnAfterRenderAsync
的数据之间实现这个 "dependency" ?
不完美,但对我有用, 让你等待下载
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;
}
}