Blazor - 如何动态创建组件

56

我想测试是否可以动态创建Blazor组件。

我找不到任何方法来实现这一点。我尝试过在此链接中找到的一些动态内容,但是没有得到任何结果。


请添加更多的用例和代码,确切地说您想要实现什么?您尝试了什么,它在哪里失败了? - Flores
抱歉回复晚了 - 你的答案满足了我的需求,谢谢。 - Diemauerdk
2
https://learn.microsoft.com/en-us/aspnet/core/blazor/advanced-scenarios?view=aspnetcore-3.1#manual-rendertreebuilder-logic - Jinjinov
4个回答

54
根据已接受答案和本答案的原始版本上的评论,我认为关于动态添加组件可能有点混淆。有(至少)几种方法可以实现这一点(以及许多现有的问题,例如here)。这完全取决于您对“动态”具体指的是什么:
1)在Razor代码中使用条件语句
如果您要实现的目标只是基于数据或模型中的某个状态显示或隐藏组件,则动态呈现组件的“正常”方式是在Razor视图中使用某种条件代码。
简单条件渲染
@if (_showCounter)
{
  <MyCounterComponent Count="@_count" />
}

@code {
  bool _showCounter = true;
  int _count;
}

简单的重复数据集

对于重复的数据集,例如项目列表,您可以利用Blazor的数据绑定功能。

以显示销售订单并为每个销售订单行设置一个子组件的页面/组件为例。您可能在razor页面上有以下代码:

  @foreach (var salesOrderLine in _salesOrder.salesOrderlines)
  {
    <SalesOrderLine SOLine=@salesOrderLine />
  };

如果你有一个按钮可以添加另一个销售订单行,那么你可以在该按钮的点击事件中简单地将新记录添加到_salesOrder模型/视图模型中。按钮点击通常会触发重新呈现,因此页面应自动显示额外的SalesOrderLine组件(如果没有,则可以使用this.StateHasChanged();告诉它事情已经改变并引起重新呈现)。

包含不同数据类型(可能是多态的)的重复数据集

如果您的列表包含不同类型的数据,您可以使用switch语句来决定渲染哪种类型的组件,例如(来自这个GitHub问题):
<ul>
    @foreach (fruit in fruits)
    {
        switch(fruit)
        {
            case PearComponent p:
                <PearComponent ParameterOfSomeSort="p"></PearComponent>
                <li>Or render pears like this, if you want the li in it</li>
                break;
            case AppleComponent a:
                <AppleComponent></AppleComponent>
                break;
            case BananaComponent b:
                <BananaComponent></BananaComponent>
                break;
            case RaspberryComponent r:
                <RaspberryComponent></RaspberryComponent>
                break;
        }
    }
</ul>

2. 使用RenderFragment进行动态渲染

有些情况无法使用上述的Razor方法来处理。在这些情况下,RenderFragment提供了另一种动态渲染页面部分的方式。

多态列表

如果您有一个真正的多态列表(例如,一个实现相同接口或从相同类继承的对象列表),则可以使用此类型的方法来自此GitHub帖子

@page "/"

@foreach (CounterParent counter in components)
{
    RenderFragment renderFragment = (builder) => { builder.OpenComponent(0, counter.GetType()); builder.CloseComponent(); };
    <div>
        <div>Before the component</div>
        @renderFragment
        <div>Afterthe component</div>
    </div>
}

@code
{
    List<CounterParent> components = new List<CounterParent>() { new CounterParent(), new CounterChild() };
}
Blazor团队正在考虑改进如何处理多态列表

mkArtakMSFT于2019年10月1日发表评论感谢您与我们联系,@Joebeazelman。使用您拥有的转换方法将是我们的建议。我们将考虑在未来在这个领域做得更好。

结论

这里的关键点(对于来自MVC背景的人)是没有必要尝试手动将新HTML注入DOM,或者以MVC方式动态加载部分视图,Blazor会为您完成。

尽管MVC和Blazor的Razor页面语法相似,但Blazor模型在概念上更接近于React而不是MVC,理解背景中类似于阴影DOM的内容非常重要。

此页面提供了一些关于Blazor数据绑定的良好指导


20
什么?动态渲染组件非常有用且非常常见。例如,如果您处理一个多态组件列表,其中每个成员都实现其自己独特的视觉呈现方式,因为它们可能是外部插件组件,无法在编译时确定。在这种情况下,您的方法将无法胜任。 - ATL_DEV
1
嗨@ATL_DEV。这可能略微取决于“动态”的定义是多么广泛或狭窄,我已经更新了我的答案,以尝试使其更清晰。我肯定同意它是一个非常有用的设施,但我个人认为它并不常见,例如我们刚刚在Blazor中编写了一个大型业务应用程序,没有使用RenderFragment。然而,对于外部插件的多态渲染场景,RenderFragment绝对是一个极好的用途。顺便说一下,Blazor团队正在努力改进多态处理,请参见我的答案中的github链接。 - tomRedox
在现实世界中,您经常需要使用现有的基于JavaScript的解决方案,并需要在js端进行额外的设置 - 因此,您可以在Blazor端创建一个基本元素,并通知js执行所需的集成步骤。请记住,这样做需要往返传输,如果您正在处理大量元素(例如初始化),可能会导致明显的延迟 - 因此,此函数应能够同时处理多个元素。 - Puschie

33

对于版本0.2,这是Steve Sanderson的答案:

我们将来会实现更好的API来构建RenderFragments,但是目前你可以

@CreateDynamicComponent();
@functions {
    RenderFragment CreateDynamicComponent() => builder =>
    {
        builder.OpenComponent(0, typeof(SurveyPrompt));
        builder.AddAttribute(1, "Title", "Some title");
        builder.CloseComponent();
    };
}

这些是非常低级的API(甚至没有文档记录),因此我们希望现在不需要太多人这样做。稍后会推出更高级别的API。

来源在此处


19
“我们希望不需要太多人这样做” - 任何设计应用程序都必须具备动态添加组件的能力。可视化编辑器、流程建模界面、仪表板界面...因此,这不是罕见或超凡要求... - OSP
4
也许这是低级的,但它有文档 RenderTreeBuilder.OpenComponent Method - IvanH

6
<DynamicComponent>使所有这些相对轻松地实现。这是.NET 6的核心新增功能之一,该版本将于2021年11月正式发布。
我现在正在RC2上探索它,并发现使用起来非常简单,尽管有关不同用法模式的文档很少。
以下是我找到的两个最佳实现参考资料:
- 动态呈现的ASP.NET Core Razor组件 - 用于基本实现和传递数据到您的动态组件。 - Blazor .NET 6 - 动态组件 - 4个DynamicComponent示例 - 包括包装和集成组件的替代方法以及连接事件的方法。

2
当然,您也可以利用BuildRenderTree来完成类似的操作,但通常不建议这样做,因为可能会出现错误。 (Microsoft文档)
public class CustomLabel: ComponentBase
{  
    [Parameter] public string Id { get; set; }
    [Parameter] public string? Label { get; set; }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        if (!string.IsNullOrEmpty(Label))
        {
            builder.OpenElement(1, "label");
            builder.AddAttribute(2, "for", Id);
            builder.AddAttribute(3, "class", "custom-css-classes-here");
            builder.AddContent(4, Label);
            builder.CloseElement();
        }
    }

    // another example: maybe you would have an "IsRequired" flag 
    // that would add an "*" after the label if set to true
}

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