基本组件布局继承Blazor

19

假设我的大多数组件都有一个头部,我想创建一个基础组件,该组件具有头部变量,并使所有其他组件从该组件继承并设置头部。因此,我有:

BaseComponent

@inherits LayoutComponentBase;

<h1>@header</h1>

@Body

@code {

    protected string header;
}

组件

@inherits BaseComponent;

"internal component text"

@code {
  protected override void OnInitialized()
    {
         base.header = "Setting header for the parent"
    }
}

这段代码编译并没有出现错误,但基础表头没有显示出来。就好像基础部分没有被渲染一样。我错在哪里了?

附言:

我还尝试过@layout BaseComponent,甚至同时使用两个指令。


为了解决这个问题,您需要学习Blazor组件模型和布局机制。没有捷径... - enet
5个回答

35

截至目前,衍生的剃刀组件会自动实现其基类的所有方法,包括BuildRenderTree(它呈现您在razor文件中键入的HTML标记)。当您未输入任何内容时,该方法将不会尝试自行调用基本的BuildRenderTree方法。因此,您需要手动执行此操作:

@inherits BaseComponent;

@{
    base.BuildRenderTree(__builder);
}

@code {
  protected override void OnInitialized()
    {
         base.header = "Setting header for the parent"
    }
}

4
在Stack Overflow上,只提供代码的答案是被不鼓励的,因为它们没有解释如何解决问题。请编辑您的答案,解释这段代码是如何回答问题以及如何改进现有问题,以便于对问题存在相似问题的其他用户也有帮助。 - FluffyKitten
12
@FluffyKitten,你说得完全正确,当时这里是凌晨2点,我的大脑已经疲惫不堪。我加入了一些细节! - sw1337
4
__builder 来自哪里? - Daniel Robinson
这个答案需要更新 - 可能自那以后Blazor已经有所发展;__builder不存在。 - Jon Barker
@JonBarker 我刚试了一下(Blazor 7),它可以工作。然而,IDE没有显示它正常工作,但实际上构建成功(并且工作)。 - JasonS
对我来说也很好用,@JonBarker。我广泛使用它。 - Ryan Faricy

18

扩展@sw1337的回答,如果派生组件没有markdown,您可以将其创建为常规cs文件而不是razor文件,它将按预期工作,无需调用基本BuildRenderTree方法。有关详细信息,请参见这里

简单例子

基础组件(MyAbstractComponent.razor)

<h1>@header</h1>

@code {
    protected string header;
}

派生组件

public class MyDerivedComponent : MyAbstractComponent
{
    protected override void OnInitialized()
    {
         header = "set header from derived component";
    }
}

参数示例

基础组件 (MyAbstractComponent.razor)

<h1>@Header</h1>

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

派生组件

public class MyDerivedComponent : MyAbstractComponent
{
    protected override void OnParametersSet()
    {
        // coalesce
        Header ??= "Default Header from derived Component"
    }
}

用法1 - 派生组件的默认标题

一些 Razor 文件

<MyDerivedComponent></MyDerivedComponent>

结果

<h1>Default Header from derived Component</h1>

使用方法2 - 指定派生消费者的标题

某些 Razor 文件

<MyDerivedComponent Header="MyHeader"></MyDerivedComponent>

结果

<h1>MyHeader</h1>

RenderFragment 参数示例

Blazor Fiddle

MyBaseComponent.razor

<h3>@Header</h3>

@if (ChildContent is not null)
{
    @ChildContent
}

@code {
    [Parameter] public string Header { get; set; }
    [Parameter] public RenderFragment ChildContent { get; set; }
}

MyDerivedComponent.cs

public class MyDerivedComponent : MyBaseComponent
{
    protected override void OnParametersSet()
    {
        // coalesce
        Header ??= "Derived Component Header";
        ChildContent ??= CreateDefaultChildContent();
    }
        
    private static RenderFragment CreateDefaultChildContent()
    {
        return builder =>
        {
            builder.OpenElement(0, "h4");
            builder.AddContent(1, "Derived Component default child content value");
            builder.CloseElement();
        };
    }
}

使用 MyRazorPage.razor

示例一 消费者未传递参数。从派生组件默认值中使用HeaderChildContent

code

<MyDerivedComponent/>

输出

<h3>Derived Component Header</h3>
<h4>Derived Component default child content value</h4>

示例二 用户传递了Header参数,派生组件的默认ChildContent将被使用。

代码

<MyDerivedComponent Header="Consumer Header"/>

输出

<h3>Consumer Header</h3>
<h4>Derived Component default child content value</h4>

第三个例子 用户传递了HeaderChildContent两个参数。

代码

<MyDerivedComponent Header="Consumer Header">
    <h4>Child Content - from consumer</h4> 
</MyDerivedComponent>

输出

<h3>Consumer Header</h3>
<h4>Child Content - from consumer</h4>

3

2022年更新 - 这种方法在.NET 6中对我来说运行良好。

基础组件:

<h1>@Header</h1>

@code{
    public string? Header {get; set;}
}

衍生组件:

@inherits BaseComponent

@code {
    protected override void OnParametersSet()
    {
        base.Header = "New Header Value";
    }
}

这个回答似乎与问题无关,只是一个没有解释的代码示例。 - jkdba

2
以下是我使用的方法:
在我看来,Razor编译器的工作方式非常简单,它总是覆盖一个名为"BuildRenderTree"的方法。无论它是从ComponentBase、IComponent还是其他类型派生而来都没有关系。 由于我们无法让Razor编译器生成一个除了"BuildRenderTree"之外的方法名来包含在我们的基类中,所以我们只需使用"new"关键字重新声明这个方法。
唯一的问题是,它需要一个额外的类来重新声明方法的签名。

BaseComponentCore.razor

<h1>@Header</h1>
@Body

@code
{
    protected virtual string Header => "Default";
    private protected virtual RenderFragment Body => null;
}

BaseComponent.cs

public abstract class BaseComponent : BaseComponentCore
{
    private protected sealed override RenderFragment Body => BuildRenderTree;

    // Allow content to be provided by a .razor file but without 
    // overriding the content of the base class
    protected new virtual void BuildRenderTree(RenderTreeBuilder builder)
    {
    }
}

DerivedComponent.razor

@inherits BaseComponent

<p>Hello world!</p>

@code
{
    protected override string Header => "Derived component";
}

结果

结果应该是

<h1>Derived component</h1>
<p>Hello world!</p>

所以在内部,ComponentBase仍然渲染ComponentBase.BuildRenderTree(该方法在BaseComponentCore中被重写),而我们现在可以将新成员BaseComponent.BuildRenderTree作为渲染片段集成到基类中。

-1

你展示的方式永远不会起作用,因为在内部组件上设置父组件属性不会触发渲染。 据我所知,解决这个问题的正确方法是拥有一个独立的服务类,它保存一些共同状态(在你的情况下,当前页面标题),并通过事件操作的方式注入到每个需要更改它的组件中:

实现的代码应该类似于:

public class GlobalUtilities
{
    public string MainPageTitle { get; private set; } = "Home (default value)";

    public event Action OnMainTitleChanged;

    public void SetMainTitle(string text)
    {
        MainPageTitle = text;
        OnMainTitleChanged.Invoke();
    }
}

在 Startup.cs 中将其注册到管道中:

services.AddScoped<GlobalUtilities>();

应该将其注册为作用域,因为它的生命周期与当前连接配对。

然后在您需要的所有后代组件中使用它:

组件

//No need to inherit, only inject...
// @inherits BaseComponent;

@inject GlobalUtilities gu

<p>Something here</p>

@code {
    protected void OnInitialized()
    {
        gu.SetMainTitle("Whatever you want");
    }
}

希望能对你有所帮助!


父组件的静态内容(我有一些测试词)也没有显示出来...就好像父组件不存在一样。 - americanslon
这个解决方案似乎对继承的工作原理存在误解。使用派生组件时,它具有基础实现和自身实现的所有功能。在运行时触发基础组件的更改意味着对象在运行时是分开的实体。原因在于,由于 .razor 文件始终实现 BuildRenderTree 方法,因此派生组件覆盖了基础组件的 BuildRenderTree 方法,否则开发人员每次都需要实现。 - jkdba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接