【问题标题】:Blazor templating component with inheritance具有继承的 Blazor 模板组件
【发布时间】:2021-11-14 19:55:56
【问题描述】:

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

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

Index.razor

@page "/"

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

<PetDog @ref="dog" />

@code {
    PetDog dog;

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

宠物狗剃须刀

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

【问题讨论】:

    标签: c# blazor blazor-component


    【解决方案1】:

    当你使用

    <PetDog @ref="dog" />
    
    @code {
        PetDog dog;
    
        void ShowPetDetails()
        {
            dog.ToggleDisplay();
        }
    }
    

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

    宠物模板.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());
        }
    }
    

    宠物狗剃须刀

    @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# 类,但您不能将它们视为普通类。他们的行为不同。例如,您不能在组件之外定义变量实例,并设置其参数等。充其量,您可以捕获对组件的引用以及调用组件实例上的公共方法,就像在当前示例中所做的那样。简而言之,组件对象不同于普通的类。

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

    但只是想知道如何从外部更改组件参数值,它继承/使用模板。我尝试了文档中的方法或找到的资源,但对我的情况不起作用

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

    <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。参数属性应该是自动属性;也就是说,有一个像这样的 get 和 set 访问器:[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,不应修改。如果您希望对参数值进行一些操作,请将其复制到局部变量中,然后执行您的操作。

    【讨论】:

    • 我是否必须为每个新的“Pet”组件使用“PetDog”的代码部分?这会很乏味:(我应该回到js吗:P
    • 不,请不要去JS。只需学习 Blazor 组件模型以及如何使用它。了解什么是组件,它与普通类有何不同,以及如何设计组件。
    • 不,我哪儿也不去,开个玩笑。但只是想知道如何从外部更改组件参数值,继承/使用模板。我尝试了文档中的方法或找到的资源,但对我的情况不起作用。
    • 好的。你的答案有效。但是为每个新的“宠物”组件重复PetDog 中的代码块对我来说仍然很烦人。我希望为即将推出的 blazor 版本提供更好的解决方案。
    • “我希望为即将推出的 blazor 版本提供更好的解决方案。”您实际上是指正确的解决方案,而不是更好的解决方案。我的解决方案是错误设计的最佳解决方案。但我永远不会那样编码。我猜您想要的是使用模板组件,其中模板组件获取通用参数类型,以便您可以将类型参数 TItem 传递给模板,其基础类型可能是 Dog 对象或 Cat 对象等. 查找模板化组件的文档
    【解决方案2】:

    正如@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";
    }
    

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

    【讨论】:

    • SO 格式由于某种原因搞砸了,我无法修复它。所以请原谅凌乱的代码格式。
    • 对不起,也许我错过了一些东西,但你的代码没有运行。
    • 错误是什么?
    • 不要像我一样做 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 1970-01-01
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多