Blazor OnAfterRenderAsync Javascript

时间:2019-12-13 04:19:26

标签: c# asp.net-core blazor-client-side

因此,我一直在玩Blazor WebAssembly,我不知道如何解决此问题。基本上,我有一个NavMenu.razor页面,该页面将从JSON文件异步获取NavMenuItem数组。很好现在,我想做的是向下面的foreach循环中的import SwiftUI import PlaygroundSupport import Combine final class Testing: ObservableObject { @Published var items: [String] = [] init() {} init(items: [String]) { self.items = items } } struct SVItem: View { var value: String init(value: String) { print("init SVItem: \(value)") self.value = value } var body: some View { Text(value) } } struct HSItem: View { var value: String init(value: String) { print("init HSItem: \(value)") self.value = value } var body: some View { Text(value) } } public struct PlaygroundRootView: View { @EnvironmentObject var testing: Testing public init() {} public var body: some View { VStack{ Text("ScrollView") ScrollView(.horizontal) { HStack() { ForEach(self.testing.items, id: \.self) { value in SVItem(value: value) } } .background(Color.red) } .frame(height: 50) .background(Color.blue) Spacer() Text("HStack") HStack { ForEach(self.testing.items, id: \.self) { value in HSItem(value: value) } } .frame(height: 30) .background(Color.red) Spacer() Button(action: { print("APPEND button") self.testing.items.append("A") }, label: { Text("APPEND ITEM") }) Spacer() Button(action: { print("SET button") self.testing.items = ["A", "B", "C"] }, label: { Text("SET ITEMS") }) Spacer() } } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = UIHostingController( // problem 1 rootView: PlaygroundRootView().environmentObject(Testing()) // problem 2 // rootView: PlaygroundRootView().environmentObject(Testing(items: ["1", "2", "3"])) ) 标记生成的每个锚添加一个事件侦听器。

该循环外部的NavLink(未由async函数填充的NavLink)已成功正确地应用了addSmoothScrollingToNavLinks()函数。其他NavLink则没有。似乎它们还没有加入DOM。

我猜有一些奇怪的比赛条件,但我不知道如何解决。关于如何解决此问题的任何想法?

NavMenu.razor

<NavLink>

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

@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; }
    }
}

3 个答案:

答案 0 :(得分:1)

这是因为Blazor将等待df.corr()完成,并且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!
        }
        ...

如您所见,OnInitializedAsync可能会在StateHasChanged()完成之前开始。由于您是通过OnInitializedAsync()方法发送HTTP请求的,因此该组件会在您从OnInitializedAsync()端点获得响应之前进行渲染。

如何修复

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

sample-data/navmenuitems.json

其中@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

答案 1 :(得分:1)

我也在尝试正确使用此模式,在OnInitializedAsync中调用一些异步服务,然后在OnAfterRenderAsync上调用一些JavaScript。事实是,javascript调用在那里执行一些js,这取决于服务返回的数据(数据必须在其中才能使所需的行为生效)。

发生的事情是js调用在数据到达之前运行,是否有一种方法可以实现OnInitializedAsyncOnAfterRenderAsync的数据之间的这种“依赖关系”?

答案 2 :(得分:1)

不完美,但对我有用, 让你等待下载

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