简体   繁体   English

动态生成的 Blazor 页面仅在页面导航之间部分更新

[英]Dynamically generated Blazor pages are only partially updated between page navigation

I have a list of pages, each using the same Blazor page component, of which the content is generated dynamically based on the page name.我有一个页面列表,每个页面都使用相同的 Blazor 页面组件,其中的内容是根据页面名称动态生成的。 The content is split across different tabs.内容分布在不同的选项卡中。 Due to some Blazor rendering issue, when navigating between pages, not everything is updated, resulting in the update of my tab content, but my tab headers are not.由于某些 Blazor 渲染问题,在页面之间导航时,并非所有内容都更新,导致我的选项卡内容更新,但我的选项卡标题没有更新。

Everything is done in .NET Core 3.1 LTS, as I am not able to upgrade to .NET 5 yet due to various other constraints.一切都在 .NET Core 3.1 LTS 中完成,因为由于其他各种限制,我还无法升级到 .NET 5。

Given, as an example, the following page content:例如,以下页面内容:

pageContent.Add("page-1", new Dictionary<string, string>
{
    { "tab a", "This is page 1, tab a" },
    { "tab b", "This is page 1, tab b" },
    { "tab c", "This is page 1, tab c" }
});

pageContent.Add("page-2", new Dictionary<string, string>
{
    { "tab d", "This is page 2, tab d" },
    { "tab e", "This is page 2, tab e" }
});

The result is the following:结果如下:

第 1 页 第2页

As you can see, on page 2 the content is updated, but the tab headers are not.如您所见,第 2 页上的内容已更新,但选项卡标题未更新。 However, if I move back from page 2 to page 1, the content of page 1 is displayed, but now with the tab headers of page 2 are shown.但是,如果我从第 2 页移回第 1 页,则会显示第 1 页的内容,但现在显示第 2 页的选项卡标题。

My tab control is based on various samples found online, and although it contains a lot more functionality, I tried to reduce it to a very basic working example which reproduces the problem.我的选项卡控件基于在线找到的各种示例,虽然它包含更多功能,但我试图将其简化为一个非常基本的工作示例,可以重现该问题。

PageContentService.cs页面内容服务.cs

public Dictionary<string, string> GetPageContent(string pageName)
            => pageContent.ContainsKey(pageName) ? pageContent[pageName] : new Dictionary<string, string>();

DynamicPage.razor动态页面.razor

@page "/dynamic/{PageName}"

<MyDynamicContent PageName="@PageName"/>

@code {
    [Parameter]
    public string PageName { get; set; }
}

MyDynamicContent.razor MyDynamicContent.razor

@if (isLoading)
{
    <p>Loading...</p>
}
else
{
    <MyTabs SelectedTab="@selectedTabTitle" OnSelectedTabChanged="OnSelectedTabChanged">
        @foreach (var (tabId, tabContent) in currentPage)
        {
            <MyTabItem Title="@tabId">
                @tabContent
            </MyTabItem>
        }
    </MyTabs>
}

@code {
    private bool isLoading;
    private string selectedTabTitle;
    private Dictionary<string, string> currentPage;

    [Parameter]
    public string PageName { get; set; }

    [Inject]
    public IPageContentService PageContentService { get; set; }

    protected override void OnParametersSet()
    {
        base.OnParametersSet();

        isLoading = true;
        currentPage = PageContentService.GetPageContent(PageName);

        if (currentPage != null && currentPage.Count > 0)
        {
            selectedTabTitle = currentPage.First().Key;
        }

        isLoading = false;
    }

    public void OnSelectedTabChanged(string title)
        => selectedTabTitle = title;

}

MyTabs.razor MyTabs.razor

<CascadingValue Value="@(this)" IsFixed="true">
    <CascadingValue Value="@selectedTabName">
        <div>
            <ul class="nav nav-tabs">
                @foreach (var item in tabItems)
                {
                    var aCss = "nav-link";
                    if (item.Active)
                    {
                        aCss += " active show";
                    }

                    <li class="nav-item">
                        <a class="@aCss" tabindex="0" @onclick="async () => await item.OnTabClicked()">
                            @item.Title
                        </a>
                    </li>
                }
            </ul>

            <div class="tab-content">
                @ChildContent
            </div>
        </div>
    </CascadingValue>
</CascadingValue>

@code {
    private readonly List<MyTabItem> tabItems = new List<MyTabItem>();
    private string selectedTabName;

    [Parameter]
    public string SelectedTab
    {
        get => selectedTabName;
        set
        {
            if (selectedTabName != value)
            {
                selectedTabName = value;
                OnSelectedTabChanged.InvokeAsync(value);
            }
        }
    }

    [Parameter]
    public EventCallback<string> OnSelectedTabChanged { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public IReadOnlyList<MyTabItem> TabItems
        => tabItems;

    public async Task ChangeSelectedTab(string title)
    {
        SelectedTab = title;
        await InvokeAsync(StateHasChanged);
    }

    public async Task AddTab(MyTabItem tabItem)
    {
        if (tabItems.All(x => x.Title != tabItem.Title))
        {
            tabItems.Add(tabItem);
            await InvokeAsync(StateHasChanged);
        }
    }

    public async Task RemoveTab(MyTabItem tabItem)
    {
        if (tabItems.Any(x => x.Title == tabItem.Title))
        {
            tabItems.Remove(tabItem);
            await InvokeAsync(StateHasChanged);
        }
    }
}

MyTabItem.razor MyTabItem.razor

@implements IAsyncDisposable

@{
    var css = "tab-pane";

    if (Active)
    {
        css += " active show";
    }
}

<div class="@css">
    @if (Active)
    {
        @ChildContent
    }
</div>    

@code {

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    [CascadingParameter]
    public MyTabs ParentTabs { get; set; }

    public bool Active
        => ParentTabs.SelectedTab == Title;

    public async Task OnTabClicked()
    {
        if (ParentTabs != null)
        {
            await ParentTabs.ChangeSelectedTab(Title);
        }
    }

    protected override async Task OnInitializedAsync()
    {
        if (ParentTabs != null)
        {
            await ParentTabs.AddTab(this);
        }

        await base.OnInitializedAsync();
    }

    public ValueTask DisposeAsync()
        => ParentTabs != null ? new ValueTask(ParentTabs.RemoveTab(this)) : new ValueTask(Task.CompletedTask);

}

My question is very simple: Why is my tab content updating, but my tab headers are not?我的问题很简单:为什么我的选项卡内容会更新,而我的选项卡标题却没有? And how do I get them to update when navigating between pages?在页面之间导航时如何让它们更新?

Although adding the @key property to each list isn't a bad idea, as @MisterMagoo suggested in the comments, it wasn't wat ultimately fixed it.尽管将@key属性添加到每个列表并不是一个坏主意,正如@MisterMagoo 在评论中所建议的那样,但最终并没有解决它。

The trick was to make every async method synchronous.诀窍是使每个异步方法同步。

MyTabs.razor MyTabs.razor

    public void ChangeSelectedTab(string title)
    {
        SelectedTab = title;
        StateHasChanged;
    }

    public void AddTab(MyTabItem tabItem)
    {
        if (tabItems.All(x => x.Title != tabItem.Title))
        {
            tabItems.Add(tabItem);
            StateHasChanged;
        }
    }

    public void RemoveTab(MyTabItem tabItem)
    {
        if (tabItems.Any(x => x.Title == tabItem.Title))
        {
            tabItems.Remove(tabItem);
            StateHasChanged;
        }
    }

MyTabItem.razor MyTabItem.razor

    public void OnTabClicked()
    {
        ParentTabs?.ChangeSelectedTab(Title);
    }

    protected override void OnInitialized()
    {
        ParentTabs?.AddTab(this);
        base.OnInitialized();
    }

    public void Dispose()
    {
        ParentTabs?.RemoveTab(this);
    }

So the async methods seem to really change how the render pipeline behaves, and do things seemingly out of order.所以异步方法似乎真的改变了渲染管道的行为方式,并且做的事情看起来似乎是乱序的。 I guess that makes a bit sense, and might have some benefits too it, but it isn't what I needed in this scenario.我想这有点道理,也可能有一些好处,但这不是我在这种情况下所需要的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM