【问题标题】:Create a recursive list structure with Blazor使用 Blazor 创建递归列表结构
【发布时间】:2021-07-21 19:06:59
【问题描述】:

我正在尝试在 Blazor 中创建一个递归列表 <ul>,我这样做的方式看起来合乎逻辑,但我的行为很奇怪,这是我的代码:

代表ul li的类元素:

public class Element
{
    public string Title { get; set; }
    public Element Parent { get; set; }
    public int Index { get; set; }
}

ULComponent.Razor:

@if (DataItems?.Count() > 0)
{
  <ul>
        @foreach (var element in DataItems)
        {
            var children = DataItems.Where(e => e.Parent == element).OrderBy(e => e.Index);
            <li>
                @element.Title

                @if (children.Count() > 0)
                {
                    <UlComponent DataItems="children" />
                }
            </li>
        }
  </ul>
}

ULComponent.Razor.cs,后面的代码:

public partial class ULComponent
{
    [Parameter]
    public IEnumerable<Element> DataItems { get; set; }
}

Razor 页面背后的代码:

protected override void OnInitialized()
{
    var root = new Element { Title = "Root", Index = 1 };
    var element1 = new Element { Title = "Element 1", Index = 1, Parent = root };
    var element2 = new Element { Title = "Element 2", Index = 2, Parent = root };
    var element3 = new Element { Title = "Element 3", Index = 3, Parent = root };
    var element11 = new Element { Title = "Element 1.1", Index = 1, Parent = element1 };
    var element12 = new Element { Title = "Element 1.2", Index = 2, Parent = element1 };
    var element121 = new Element { Title = "Element 1.2.1", Index = 1, Parent = element12 };
    var element21 = new Element { Title = "Element 2.1", Index = 1, Parent = element2 };
    var element22 = new Element { Title = "Element 2.2", Index = 2, Parent = element2 };
    var element31 = new Element { Title = "Element 3.1", Index = 1, Parent = element3 };
    Elements = new List<Element> {root, element1, element2, element3, element11,
           element12, element121, element21, element22, element31};
}

剃刀页面:

@if (Elements != null)
{
    <div class="row">
        <ULComponent DataItems="Elements"/>
    </div>
    
}

代码向上的结果。

Root
    Element 1
    Element 2
    Element 3
Element 1
    Element 1.1
    Element 1.2
Element 2
    Element 2.1
    Element 2.2
Element 3
    Element 3.1
Element 1.1
Element 1.2
    Element 1.2.1
Element 1.2.1
Element 2.1
Element 2.2
Element 3.1

我想得到这个:

Root
Element 1
    Element 1.1
    Element 1.2
        Element 1.2.1
Element 2
    Element 2.1
    Element 2.2
Element 3
    Element 3.1

【问题讨论】:

  • 不应该&lt;MyReport DataItems="children" /&gt;&lt;ULComponent /&gt;
  • 对不起,我更正了!
  • FWIW,你得到的结果是我对你的代码的期望。在第一个循环中,您的列表中的每个项目都有一个&lt;li&gt;,并且对于其中的每个项目,都有一个&lt;ul&gt;,只有它们的直系子级。如果您不想失去孩子的孩子,您需要以某种方式将整个集合传递给子组件

标签: c# asp.net-core blazor


【解决方案1】:

组件可以嵌套在它们自己内部。只要您有正确的自引用数据,就很容易解压它。这样做的好处是,如果您稍后决定要将“动物”归类为“事物”,您只需将其 ParentID 更改为 8,即可完成。

Element.cs

public class Element
{
    public int ID { get; set; }
    public int? ParentID { get; set; }
    public string Name { get; set; }
}

Parent.razor

<CascadingValue Value="Items">
    <ul>
        <RecursiveUI ParentID="null" />
    </ul>
</CascadingValue>
 
@code {
    List<Element> Items;

    protected override void OnInitialized()
    {
        Items = new List<Element>
        {
            new Element { ID=1, Name="Animals", ParentID = null},
            new Element { ID=2, Name="Mammals", ParentID = 1},
            new Element { ID=3, Name="Birds", ParentID = 1},
            new Element { ID=4, Name="Bears", ParentID = 2},
            new Element { ID=5, Name="Beavers", ParentID = 2},
            new Element { ID=6, Name="Eagles", ParentID = 3},
            new Element { ID=7, Name="Parakeets", ParentID = 3},
            new Element { ID=8, Name="Things", ParentID = null},
            new Element { ID=9, Name="Yo-yos", ParentID = 8},
            new Element { ID=10, Name="Computers", ParentID = 8}
        };
    }
}

RecursiveUI.razor

@foreach (var item in Items.Where(i => i.ParentID == ParentID))
{
    <li>@item.Name
        @if (Items.Where(c=>c.ParentID == item.ID).Any())
        {
           <ul>
               <RecursiveUI ParentID="item.ID"/>  @*THIS IS THE MAGIC.*@
           </ul>
        }
    </li>
}

@code {
    [Parameter]
    public int? ParentID { get; set; }

    [CascadingParameter]
    List<Element> Items { get; set; }
}

【讨论】:

  • [CascadingParameter] 是我今天很高兴学到的东西!
【解决方案2】:

不是一个完整的答案,但我不能将代码 sn-ps 放在评论中。

你有什么理由不能像下面这样重组你的班级吗?

public class Element
{
    public string Title { get; set; }
    public Element Parent { get; set; }
    public int Index { get; set; }
    public IEnumerable<Element> SubElements { get; set; }
}

它节省了大量的 Linq 查询来获取子列表,然后你可以 递归遍历父节点并构建您的控件。

【讨论】:

    【解决方案3】:

    你说你的代码看起来合乎逻辑,但是这样想:

    在您的第一次迭代中,您创建了一个&lt;ul&gt;,并为列表中的每个项目创建了一个&lt;li&gt;,它给出:

    Root
    Element 1
    Element 2
    Element 3
    Element 1.1
    Element 1.2
    Element 1.2.1
    Element 2.1
    Element 2.2
    Element 3.1
    

    请注意,这已经违背了您的意愿

    然后,为每个项目创建一个 ULComponent 及其直接子项,因此对于 Root,您创建一个 ULComponentchildren={Element 1, Element 2, Element 3}。这将创建一个带有 3 个 &lt;li&gt;s 的 &lt;ul&gt;

    Root
        Element 1
        Element 2
        Element 3
    Element 1
    Element 2
    Element 3
    Element 1.1
    Element 1.2
    Element 1.2.1
    Element 2.1
    Element 2.2
    Element 3.1
    

    你不会走得更远,因为这三个元素都没有任何子元素在当前列表中(即Element 1, Element 2, Element 3)。

    这就是为什么你应该重新考虑你的 Element 类以包含孩子,或者找到一种方法来传递两个参数而不是一个:当前元素 完整列表

    未经测试,但对它的外观有所了解:

    public partial class ULComponent
    {
        [Parameter]
        public Element Item { get; set; }
        [Parameter]
        public IEnumerable<Element> DataItems { get; set; }
    }
    
    <ul>
      <li>
        @Item.Title
        @foreach (var child in DataItems.Where(e => e.Parent == Item).OrderBy(e => e.Index))
        {
          <ULComponent Item="child" DataItems="DataItems" />
        }
      </li>
    </ul>
    

    【讨论】:

    • 非常感谢您的帮助,请不要为我和其他人删除您的解决方案!
    【解决方案4】:

    请原谅我添加了另一个答案,但对于很长的列表,可能值得添加一些简单的函数来选择要显示的分支。

    从我的其他答案,但更改以下内容:

    public class Element
    {
        public int ID { get; set; }
        public int? ParentID { get; set; }
        public string Name { get; set; }
        public bool IsOpened {get; set;}  /*Add This*/
    }
    

    并更改 RecursiveUI.razor 如下:

    <li style="cursor:pointer" @onclick="() => item.IsOpened = !item.IsOpened">@item.Name
        @if (item.IsOpened && Items.Where(c => c.ParentID == item.ID).Any())
        {
            <ul>
                <RecursiveUI ParentID="item.ID" />
            </ul>
        }
    </li>
    

    【讨论】:

    • Blazor 的效率惊人。您可以尝试在 Recursive UI 中设置断点,并查看它不是每次都重新构建所有项目。或者尝试制作一个更长的列表(比如数百个项目),但这真的不会有什么不同。我做过这样的树,其中最深的文本是整本书的文本(如绿野仙踪等),而 Blazor 甚至没有眨眼。