Blazor - 如何动态创建组件

Blazor - How to create Components dynamically

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

我找不到任何方法来做到这一点。我对 this link 上的一些动态内容进行了一些试验,但没有得到任何结果。

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

We'll implement nicer APIs to build RenderFragments in the future, but for now you can

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

Those are very low-level APIs (not even documented) so we hope not many people need to do this right now. Higher-level APIs for this will come later.

找到here

从对已接受答案和该答案原始版本的评论来看,我认为动态添加组件可能有些混乱。有(至少)几种方法可以实现这一点(以及一些关于此的现有问题,例如 here)。这完全取决于 'dynamically':

的确切含义

1) 在 Razor 代码中使用条件语句

如果您想要实现的只是根据数据或模型中的某些状态显示或隐藏组件,那么 'normal' 动态呈现组件的方法是使用某种条件Razor 视图中的代码。

简单条件渲染

@if (_showCounter)
{
  <MyCounterComponent Count="@_count" />
}

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

简单的重复数据集

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

以显示销售订单的 page/component 为例,然后每个销售订单行都有一个子组件。您的剃刀页面上可能有如下所示的代码:

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

如果您有一个添加了另一个销售订单行的按钮,那么您只需将新记录添加到该按钮单击事件中的 _salesOrder model/view 模型。 单击按钮通常会触发 re-render,因此页面应该会自动显示一个额外的 SalesOrderLine 组件(如果没有,您可以使用 this.StateHasChanged(); 告诉它事情不同并导致re-render)

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

如果您的列表包含不同的类型,您可以使用 switch 语句来决定要呈现哪种类型的组件,例如(来自 this GitHub question):

<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 提供了另一种动态呈现页面部分的方法。

多态列表

如果你有一个真正的多态列表(例如,所有实现相同接口或继承相同 class 的对象列表),那么可以使用这种类型的方法 from this GitHub post:

@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 团队 considering improving how polymorphic lists are handled 在 Blazor:

mkArtakMSFT commented on 1 Oct 2019 Thanks for contacting us, @Joebeazelman. Using the switch approach you have will be our recommendation. We will consider doing something better in this area in the future.

结论

这里的关键点(对于那些来自 MVC 背景的人)是不需要尝试手动将新的 HTML 注入 DOM 或动态加载局部视图,在正如您在 MVC 中可能采用的方式,Blazor 会为您做到这一点。

尽管 MVC 的剃刀页面和 Blazor 的剃刀页面之间的语法相似,但 Blazor 模型在概念上更接近于 React 而不是 MVC,理解有一些东西是非常重要的shadow-DOM 在后台。

This page has some good pointers 关于 Blazor 中的数据绑定。

当然你也可以利用 BuildRenderTree 做这样的事情,但通常不推荐,因为它会把事情搞砸。 (Microsoft documentation)

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
}

<DynamicComponent> 使这一切变得相对微不足道。它是 .NET 6 的核心新增功能之一,将于 2021 年 11 月正式发布。

我现在正在 RC2 上探索它,发现它使用起来非常简单,尽管关于不同使用模式的文档很少。

这是我找到的两个最佳实施参考资料;