具有继承性的 Blazor 模板组件

Blazor templating component with inheritance

我有一个基本组件 PetTemplate 和第二个 PetDog 继承并使用 PetTemplate 的模板。 PetTemplate 有一个名为 ToggleDisplay 的方法。我的目标是当我单击 Index 页面上的按钮时调用 PetDog.ToggleDisplay 方法和 show/hide 页面上的 PetDog 详细信息。

下面示例代码中的“内部”按钮有效,但“外部”按钮无效。如何从页面或父组件正确调用 ToggleDisplay 方法?

Index.razor

@page "/"

<button @onclick="ShowPetDetails">Show Details (Outside)</button>

<PetDog @ref="dog" />

@code {
    PetDog dog;

    void ShowPetDetails()
    {
        dog.ToggleDisplay();
    }
}

PetDog.razor

@inherits PetTemplate

<PetTemplate Name="Dog">
    <div>Someone's best friend!</div>
</PetTemplate>

PetTemplate.razor

<div class="mt-3">
    <button @onclick="ToggleDisplay">Show Details (Inside)</button>
    <h3>Pet Name: @Name</h3>
    <div style="display:@display">
        @ChildContent
    </div>
</div>

@code {
    string display = "none";

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

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

    public void ToggleDisplay()
    {
        display = display == "none" ? "block" : "none";
        StateHasChanged();
    }
}

当你使用

<PetDog @ref="dog" />

@code {
    PetDog dog;

    void ShowPetDetails()
    {
        dog.ToggleDisplay();
    }
}

您实际上创建了对 PetDog 组件的引用,然后尝试在您没有引用的对象(PetTemplate 的实例)上调用派生方法 dog.ToggleDisplay()。为了使其工作,您必须获得对父组件 (PetTemplate) 的引用,并将其提供给派生组件 (PetDog),如下所示:

PetTemplate.razor

<div class="mt-3">
    <button @onclick="ToggleDisplay">Show Details (Inside)</button>
    <h3>Pet Name: @Name</h3>
    <div style="display:@display">
        @ChildContent
    </div>
</div>

@code {
    string display = "none";
    string val;

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

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

    public void ToggleDisplay()
    {
        display = display == "none" ? "block" : "none";
       
        InvokeAsync(() => StateHasChanged());
    }
}

PetDog.razor

@inherits PetTemplate

<PetTemplate @ref="petTemplate" Name="Dog">
    <div>Someone's best friend!</div>
</PetTemplate>

@code
{
    PetTemplate petTemplate;

    public PetTemplate PetTemplateProp { get; set; }

    protected override void OnAfterRender(bool firstRender)
    {
        if(firstRender)
         {
            PetTemplateProp = petTemplate;
          }
        base.OnAfterRender(firstRender);
    }
}

Index.razor

@page "/"

<button @onclick="ShowPetDetails">Show Details (Outside)</button>


<PetDog @ref="dog" />

@code {
    PetDog dog;

    void ShowPetDetails()
    {

        dog.PetTemplateProp.ToggleDisplay();
      
    }
}

注意:尽管 Razor 组件是 C# 类,但您不能将它们视为普通组件 类。他们的行为不同。例如,您不能在组件外部定义变量实例并设置其参数等。充其量,您可以捕获对组件的引用并在组件实例上调用 public 方法,就像在当前示例中所做的那样。简而言之,组件对象不同于普通的 类.

同样重要的是要记住,每个组件都是一个单独的岛,可以独立于其父项和子项进行渲染。

But just wondering how can I change a component parameter value from outside of it, that inherited/uses a template. I tried the methods in the documentation or the resources I found, but it didn't work for my case

您不应该(这是一个警告)并且可能不能(现在可能是一个错误)在组件之外更改组件参数的值。例如,您不能捕获对组件的引用并为其参数赋值 属性:

<PetTemplate @ref="petTemplate">
    <div>Someone's best friend!</div>
</PetTemplate>

PetTemplate petTemplate; 

这是不允许的:petTemplate.Name="Dog" 因为这是在其组件之外更改参数。你只能这样做:

<PetTemplate Name="Dog">
    <div>Someone's best friend!</div>
</PetTemplate>

此外,从组件本身内部修改参数 属性 已被弃用(目前您应该收到警告,至少这是 Steve Sanderson 向 Blazor 团队建议的)。 为明确起见,您不应修改 PetTemplate 组件中的参数 属性 Name。一个参数属性应该是自动的属性;也就是说,有一个像这样的获取和设置访问器:[Parameter] public string Name { get; set; } 你不应该这样使用它:

private string name;

[Parameter]
public string Name 
{ 
  get => name;
  set 
  { 
     if (name != value)
     {
        name = value;

       // Code to a method or whatever to do something
     }
  } 
}

这已被弃用,因为它可能有副作用。组件参数应视为 DTO,不应修改。如果你想对参数值进行一些操作,那么将它复制到一个局部变量,然后做你的事。

正如@enet Blazor 所指出的那样,组件继承的行为并不完全符合人们的直觉预期。当您想要控制可以在内部和外部控制的 UI 功能时,这是一种更简洁的方法:

  1. 在基础组件中声明一个事件,该事件在 UI 状态从组件内部更改时引发。还让控制状态的变量成为参数。在你的情况下,像

PetTemplate.razor:

[Parameter]
public EventCallback OnToggleRequested {get;set;}

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

protected async Task RaiseToggle()
{
    await OnToggleRequested.InvokeAsync(); 
}
  1. 在您的 PetDog 中,在引发内部点击时简单地调用切换方法

PetDog.razor:

<button @onclick="RaiseToggle">Show Details (Inside)</button>
  1. 在您的容器中(在本例中为 index.razor)侦听事件并进行更改。也将外部按钮连接到相同的方法:

Index.razor:

<button @onclick="ToggleDisplay">Show Details (Outside)</button>
 
<PetDog OnToggleRequested="ToggleDisplay" Display="@display"/>
 
string display = "block";
 
void ToggleDisplay() 
{
    display = display == "none" ? "block" : "none";
}

请注意,事件可以在层次结构级别使用,您不需要在任何地方捕获任何引用。