【问题标题】:Blazor Nested ComponentsBlazor 嵌套组件
【发布时间】:2021-12-21 06:10:19
【问题描述】:

我希望能够以声明方式将 blazor 组件列表作为参数。

组件 1:

@foreach(Component2 input in Inputs)
{
    @input
}
 
@code{
    [Parameter]
    public List<Component2> Inputs {get; set;}
}

组件 2:

<h1>@Header</h1>

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

我想如何使用它:

<Component1>
    <Inputs>
        <Component2 Header="hello" />
        <Component2 Header="world" />
    </Inputs>
</Component1>

有谁知道这是否可行?

谢谢你, 特拉维斯

【问题讨论】:

  • Blazor 不是 WinForms。您不拥有(创建)组件。
  • 这不是 Blazor 模式。您可以构建自己的渲染树,但尽可能避免使用它。尤其如此,因为有一种更简单、更清洁的方法可以实现相同的输出。

标签: c# blazor


【解决方案1】:

一种选择是覆盖BuildRenderTree。在这里,我们定义了一个 MessagesComponent,它提供了一个 MessageComponents 列表,每个列表都有一条消息:

// YourPage.Razor
<p>Here are some messages:</p>
<MessagesComponent MessageComponents="messageComponents" />

@code {
    private readonly IEnumerable<MessageComponent> messageComponents = new List<MessageComponent>
    {
        new MessageComponent { Message = "Hello" },
        new MessageComponent { Message = "World" }
    };
}

// MessagesComponent.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;

namespace WebApplication3.Pages
{
    public class MessagesComponent : ComponentBase
    {
        [Parameter]
        public IEnumerable<MessageComponent> MessageComponents { get; set; }

        protected override void BuildRenderTree(RenderTreeBuilder builder)
        {
            base.BuildRenderTree(builder);

            // Render each component.
            foreach (var component in MessageComponents)
            {
                builder.OpenComponent(0, typeof(MessageComponent));
                builder.AddAttribute(1, nameof(MessageComponent.Message), component.Message);
                builder.CloseComponent();
            }
        }
    }
}

// MessageComponent.razor
@using Microsoft.AspNetCore.Components.Rendering 

<p>This message says "@Message"</p>

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

输出如下:

【讨论】:

  • 这似乎是一个不错的解决方案,但是如果第二个组件接受 RenderFragment 作为参数呢?
【解决方案2】:

在浏览网络时,我发现了一个 Blazor 大学页面的链接,该页面完美地描述了这一点。

https://blazor-university.com/templating-components-with-renderfragements/creating-a-tabcontrol/

https://github.com/mrpmorris/blazor-university/tree/master/src/TemplatedComponents/CreatingATabControl

解决方案的要点是您将父组件的引用发送到子组件。然后,当孩子初始化时,您告诉父母将孩子添加到组件列表中。

父组件:

<CascadingValue Value="this">
    <div class="btn-group" role="group">
        @foreach (Tab tabPage in Pages)
        {
            <button type="button"
                    class="btn @GetButtonClass(tabPage)"
                    @onclick=@( () => ActivatePage(tabPage) )>
                @tabPage.Text
            </button>
        }
    </div>
    @ChildContent
</CascadingValue>

@code {
    // Next line is needed so we are able to add <TabPage> components inside
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public Tab ActivePage { get; set; }
    List<Tab> Pages = new List<Tab>();
    internal void AddPage(Tab tabPage)
    {
        Pages.Add(tabPage);
        if (Pages.Count == 1)
            ActivePage = tabPage;
        StateHasChanged();
    }

    string GetButtonClass(Tab page)
    {
        return page == ActivePage ? "btn-primary" : "btn-secondary";
    }

    void ActivatePage(Tab page)
    {
        ActivePage = page;
    }
}

子组件:

@if (Parent.ActivePage == this)
{
    @ChildContent
}

@code {
    [CascadingParameter]
    private Tabs Parent { get; set; }
    [Parameter]
    public RenderFragment ChildContent { get; set; }

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

    protected override void OnInitialized()
    {
        if (Parent == null)
            throw new ArgumentNullException(nameof(Parent), "TabPage must exist within a TabControl");
        base.OnInitialized();

        Parent.AddPage(this);
    }
}

用法:

<ParentComponent>
    <ChildComponent Text="Tab 1">
        <h1>This is a test</h1>
    </ChildComponent >
    <ChildComponent Text="2">
        <h3>This is a test</h3>
    </ChildComponent >
    <ChildComponent Text="3 Tab">
        <h5>This is a test</h5>
    </ChildComponent >
</ParentComponent>

【讨论】:

    【解决方案3】:

    这里有一个简单但更通用的方法来实现你想要的。它呈现您的HeaderComponent 和一个Counter 的三个实例。

    @page "/"
    <h3>MultiHeader</h3>
    @MyComponents
    
    @code {
    
        private List<Tuple<Type, Dictionary<string, object>>> _components = new List<Tuple<Type, Dictionary<string, object>>>();
    
        protected override void OnInitialized()
        {
            _components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 1" } }));
            _components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 2" } }));
            _components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 3" } }));
            _components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(Counter), new Dictionary<string, object>()));
        }
    
        private RenderFragment MyComponents => builder =>
        {
            foreach (var component in _components)
            {
                builder.OpenComponent(0, component.Item1);
                foreach (var parameter in component.Item2)
                {
                    builder.AddAttribute(1, parameter.Key, parameter.Value);
                }
                builder.CloseComponent();
            }
        };
    }
    

    我使用了Tuple 来保持简单:如果你想变得更复杂一些,你可以有一个对象来保存你的组件信息。

    【讨论】:

      猜你喜欢
      • 2019-07-18
      • 2021-11-08
      • 2023-02-22
      • 2021-07-07
      • 2017-12-31
      • 2020-11-25
      • 2014-11-17
      • 2023-04-06
      • 2016-03-01
      相关资源
      最近更新 更多