You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Blazor中遍历ChildContent实现多子组件布局是否可行?

在Blazor中实现动态子组件的双列布局

需求描述

希望实现一个Section组件,能将传入的任意数量子组件(如MudTextField、MudTimePicker等)按每两个一行,每个占50%宽度的布局排列,使用方式如下:

MainPage.razor

<Section>
   <MudTextField Label="文本输入" />
   <MudTimePicker Label="时间选择" />
   <MudNumericField Label="数字输入" />
</Section>

期望的Section组件结构

@for (int i = 0; i < Children.Count; i += 2)
{
    <div style="width:50%">
        @Children[i]
    </div>
    @if (i + 1 < Children.Count)
    {
        <div style="width:50%">
            @Children[i + 1]
        </div>
    }
}

@code {
    [Parameter]
    public required RenderFragment ChildContent { get; set; }

    private List<RenderFragment> Children = new();

    private void ProcessChildContent()
    {
        // 需要实现逻辑:将ChildContent中的所有子组件拆分为单独的RenderFragment列表
    }
}

已尝试的方案及问题

  • 尝试用RenderTreeBuilder遍历节点但未成功,不确定是否为正确方向。
  • 无法通过不同属性接收子组件,因为需要支持未知数量的子元素。
  • 试过一种收集子元素的方案,但会导致浏览器拖拽、窗口调整或输入变化时所有子组件重新渲染;改用HashSet替代List虽缓解了问题,但并非理想方案。

可行实现方案

方案一:通过解析RenderTree捕获子元素(严格匹配需求逻辑)

以下是修正后的Section.razor实现,可稳定捕获子组件并避免不必要的重渲染:

@foreach (var row in ChildRows)
{
    <div style="display:flex; width:100%; margin-bottom:12px;">
        @foreach (var child in row)
        {
            <div style="width:50%; padding:0 8px; box-sizing:border-box;">
                @child
            </div>
        }
    </div>
}

@code {
    [Parameter, Required]
    public RenderFragment ChildContent { get; set; }

    private List<List<RenderFragment>> ChildRows { get; set; } = new();
    private RenderFragment _lastChildContent;

    protected override void OnParametersSet()
    {
        base.OnParametersSet();
        // 仅当ChildContent变化时重新处理,避免无意义重渲染
        if (!ReferenceEquals(ChildContent, _lastChildContent))
        {
            _lastChildContent = ChildContent;
            var childFragments = CaptureChildFragments();
            ChildRows = SplitChildrenIntoRows(childFragments);
        }
    }

    private List<RenderFragment> CaptureChildFragments()
    {
        var fragments = new List<RenderFragment>();
        var builder = new RenderTreeBuilder();
        ChildContent(builder);
        var frames = builder.GetFrames();

        for (int i = 0; i < frames.Count; i++)
        {
            var frame = frames[i];
            // 仅提取元素或组件类型的节点
            if (frame.FrameType is RenderTreeFrameType.Element or RenderTreeFrameType.Component)
            {
                fragments.Add(CreateIsolatedFragment(frames, i));
            }
        }
        return fragments;
    }

    private RenderFragment CreateIsolatedFragment(RenderTreeFrameCollection frames, int startIndex)
    {
        return b =>
        {
            var currentIndex = startIndex;
            var targetFrame = frames[currentIndex];
            
            // 复刻节点本身
            if (targetFrame.FrameType == RenderTreeFrameType.Element)
            {
                b.OpenElement(targetFrame.Sequence, targetFrame.ElementName);
            }
            else if (targetFrame.FrameType == RenderTreeFrameType.Component)
            {
                b.OpenComponent(targetFrame.Sequence, targetFrame.ComponentType);
                foreach (var param in targetFrame.ComponentParameters)
                {
                    b.AddAttribute(targetFrame.Sequence, param.Key, param.Value);
                }
            }

            // 复刻节点的子内容,直到遇到闭合标签
            currentIndex++;
            while (currentIndex < frames.Count)
            {
                var childFrame = frames[currentIndex];
                if (childFrame.FrameType == RenderTreeFrameType.CloseElement || childFrame.FrameType == RenderTreeFrameType.CloseComponent)
                {
                    break;
                }
                CopyFrame(b, childFrame);
                currentIndex++;
            }

            // 闭合节点
            if (targetFrame.FrameType == RenderTreeFrameType.Element)
            {
                b.CloseElement();
            }
            else if (targetFrame.FrameType == RenderTreeFrameType.Component)
            {
                b.CloseComponent();
            }
        };
    }

    private void CopyFrame(RenderTreeBuilder target, RenderTreeFrame source)
    {
        switch (source.FrameType)
        {
            case RenderTreeFrameType.Attribute:
                target.AddAttribute(source.Sequence, source.AttributeName, source.AttributeValue);
                break;
            case RenderTreeFrameType.Text:
                target.AddContent(source.Sequence, source.TextContent);
                break;
            case RenderTreeFrameType.Markup:
                target.AddMarkupContent(source.Sequence, source.MarkupContent);
                break;
        }
    }

    private List<List<RenderFragment>> SplitChildrenIntoRows(List<RenderFragment> children)
    {
        var rows = new List<List<RenderFragment>>();
        for (int i = 0; i < children.Count; i += 2)
        {
            var row = new List<RenderFragment> { children[i] };
            if (i + 1 < children.Count) row.Add(children[i + 1]);
            rows.Add(row);
        }
        return rows;
    }
}

方案二:CSS Grid简化实现(推荐)

如果不需要严格通过组件逻辑拆分子元素,直接用CSS Grid即可实现双列布局,无重渲染问题且代码极简:

<div style="display:grid; grid-template-columns:repeat(2, 1fr); gap:16px; padding:8px;">
    @ChildContent
</div>

@code {
    [Parameter, Required]
    public RenderFragment ChildContent { get; set; }
}

内容的提问来源于stack exchange,提问作者Skorpion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 21:08:19