通知 MainLayout/App 来自 Child 组件的 属性 更改

Notify MainLayout/App of Property Change from Child Component

我有一个应用程序,它包含一个带有边栏的简单布局和 header。 header 有一个显示一些自定义信息的文本(例如,用户名、日期或只是当前视图的名称)。

<Layout Sider="true">
    <LayoutSider>
        <LayoutSiderContent>
            <NavMenu />
        </LayoutSiderContent>
    </LayoutSider>
    <Layout>
        <LayoutHeader Fixed="true">
            <Header Title="@Title"></Header> <!-- CUSTOM HEADER COMPONENT -->
        </LayoutHeader>
        <LayoutContent>
            <CascadingAuthenticationState>
                <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true"> <!-- VIEW ROUTER-->
                    <Found Context="routeData">
                        <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                            <NotAuthorized>
                                You are not Authorized to Access this App. Please make sure, that you logged in with your company account.
                            </NotAuthorized>
                        </AuthorizeRouteView>
                    </Found>
                    <NotFound>
                        <LayoutView Layout="@typeof(MainLayout)">
                            <p>Sorry, there's nothing at this address.</p>
                        </LayoutView>
                    </NotFound>
                </Router>
            </CascadingAuthenticationState>
        </LayoutContent>
    </Layout>
</Layout>

@code 
{
    private string Title { get; set; }
}

注:Header是我写的一个简单的组件,只是输出一段文字,有点CSS。我还使用了 Blazorise,它将基本 HTML objects 包装到 Blazor 组件中。

我没有将此代码放入我的 MainLayout.razor 中,因为我希望在找不到页面或用户没有特定页面的权限时显示边栏和 header。

我遇到的问题是,我无法找到一个干净的解决方案来更新我的标题。目前我是这样做的,如下所示,但对我来说,它看起来像是一个非常“hacky”的解决方案,它不会在不调用 StateHasChanged.

的情况下更新标题

我制作了一个名为 ViewBase.cs 的 class,我所有的页面都继承自它并将标题作为级联参数。

<Layout Sider="true">
    <CascadingValue Value="@Title">
        <Layout>
            <LayoutHeader Fixed="true">
                ...
            </LayoutHeader>
            <LayoutContent>
                ...
            </LayoutContent>
        </Layout>
    </CascadingValue>
</Layout>

^ 调整后 App.cshtml 具有级联值

@inherits LayoutComponentBase

<CascadingValue Value="@Title">
    @Body
</CascadingValue>

@code 
{
    private string title;

    [CascadingParameter]
    public string Title
    {
        get => this.title;
        set
        {
            this.title = value;
            this.StateHasChanged();
        }
    }    
}

^MainLayout.razor

public class ViewBase : ComponentBase
{
    private string title;

    [CascadingParameter]
    public string Title
    {
        get => this.title;
        set
        {
            this.title = value;
            this.StateHasChanged();
        }
    }
}

^ViewBase.cs

这允许我通过编写 this.Title = "My custom Title!" 在我的视图中以编程方式设置标题。这实现了基本功能,但我还想直接在 Blazor 视图中控制标题。因此,我创建了一个简单的组件 HeaderControl.

@inherits Project.Shared.BaseComponents.ViewBase

@code {
    [Parameter]
    public string PTitle
    {
        get => this.Title;
        set => this.Title = value;
    }
}

然后我可以像这样在我的视图中使用这个组件:

@page "/"
@inherits Project.Shared.BaseComponents.ViewBase

<HeaderControl PTitle="Hello World!"/>

<Div Class="w-100 h-100 d-flex flex-column align-items-center justify-content-between">
    <!-- Page Content -->
</Div>

正如我所说,这有点管用,但是 StateHasChanged 有点麻烦,特别是如果您需要每秒更新几次标题(这是一项要求)。我监督过某种机制吗?我认为这会很好,如果这可以通过一个事件来处理,新值在层次结构中向上传递(View.razor -> MainLayout.razor -> App.cshtml),但我不能想办法做到这一点。我能想到的另一个解决方案是,我的 Header 组件中有一个静态的 oldscool C# 事件,它被我的 HeaderControl 组件调用。

您需要使用带有通知事件的服务。这里有一个非常简单的实现来演示原理。

一个Header服务

public class HeaderService
{
    public string Header { get; set; } = "Starting Header";

    public event EventHandler? HeaderChanged;

    public void SetHeader(string header)
    {
        Header = header;
        HeaderChanged?.Invoke(this, EventArgs.Empty);
    }

    public void NotifyHeaderChanged()
        => HeaderChanged?.Invoke(this, EventArgs.Empty);
}

Program注册:

builder.Services.AddScoped<HeaderService>();

一个Header组件

@inject HeaderService headerService
@implements IDisposable
<h3>@this.headerService.Header</h3>

@code {
    protected override void OnInitialized()
        => this.headerService.HeaderChanged += this.OnChange;

    private void OnChange(object? sender, EventArgs e)
     => this.InvokeAsync(StateHasChanged);

    public void Dispose()
    => this.headerService.HeaderChanged -= this.OnChange;
}

应用程序:

<MyHeader />
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

演示页面:

@page "/"
@inject HeaderService headerService

<PageTitle>Index</PageTitle>

<div class="p-3">
    <button class="btn btn-primary" @onclick=UpdateHeader>Say Hello</button>
</div>
Welcome to your new app.

@code {

    private void UpdateHeader()
      => this.headerService.SetHeader($"Clicked at {DateTime.Now.ToLongTimeString()}");

}