- UI组件
- 前端
【免费下载链接】ant-design-blazor
🌈A rich set of enterprise-class UI components based on Ant Design and Blazor.
本指南以 Ant Design Blazor 文档站中「自定义渲染」(Custom Render)演示为核心,讲解如何借助
SegmentedItem的ChildContent(RenderFragment)为每个分段项注入任意 UI 内容——从「头像 + 用户名」到「季节 + 月份」的双行信息卡片。读完本文,你将掌握 Segmented 组件的自定义渲染机制、ChildContent与Label/Icon/Options/Labels的优先级关系,以及把自定义内容与值绑定、切换动画、禁用等内置行为无缝集成的完整方案。
一、自定义渲染要解决什么问题
Segmented(分段控制器)是 Ant Design Blazor 自v0.12.0版本开始提供的数据展示组件,用于展示多个选项并允许用户选择单个选项,常用于「切换选中项时,关联区域内容随之变化」的场景(例如视图切换、报表粒度切换)。
默认情况下,每个分段项只显示一行纯文本标签,由Labels或Options驱动,样式统一、内容单一。但在真实业务中,选项往往需要携带更丰富的信息:
- 用户选择器:每个选项需要「头像 + 姓名」的组合展示;
- 时间粒度选择:需要「季节名 + 对应月份」的上下两行信息;
- 带图标的状态选项:需要「图标 + 文字」并列展示。
这就是官方「自定义渲染」演示(custom.md 与 Custom.razor)所展示的能力:在 Blazor 中通过ChildContent(RenderFragment,即 React 生态中 ReactNode 的对应物)自由定义每一个 Segmented Item 的渲染内容,同时保持组件的选中、切换、滑动动画等既有交互行为不变。
二、完整示例:从「用户列表」到「季度日历」
官方演示 Custom.razor 给出了两个典型场景,完整代码如下:
<Segmented TValue="string"> <SegmentedItem Value="@("user1")"> <div style=" padding: 4px;"> <Avatar Src="https://joeschmoe.io/api/v1/random" /> <div>User 1</div> </div> </SegmentedItem> <SegmentedItem Value="@("user2")"> <div style="padding: 4px;"> <Avatar Style="background-color: #f56a00 ">K</Avatar> <div>User 2</div> </div> </SegmentedItem> <SegmentedItem Value="@("user3")"> <div style="padding: 4px;"> <Avatar style="background-color: #87d068;" Icon="user"></Avatar> <div>User 3</div> </div> </SegmentedItem> </Segmented> <br /> <Segmented TValue="string"> <SegmentedItem Value=@("spring")> <div style="padding: 4px;"> <div>Spring</div> <div>Jan-Mar</div> </div> </SegmentedItem> <SegmentedItem Value=@("summer")> <div style="padding: 4px;"> <div>Summer</div> <div>Apr-Jun</div> </div> </SegmentedItem> <SegmentedItem Value=@("autumn")> <div style="padding: 4px;"> <div>Autumn</div> <div>Jul-Sept</div> </div> </SegmentedItem> <SegmentedItem Value=@("winter")> <div style="padding: 4px;"> <div>Winter</div> <div>Oct-Dec</div> </div> </SegmentedItem> </Segmented>这段代码展示了自定义渲染的两个要点:
- 每个
SegmentedItem必须显式指定TValue泛型与唯一的Value——Value是切换时OnChange/ValueChanged回调携带的选中值,与显示内容完全解耦; SegmentedItem标签体内直接书写任意 Razor 内容(ChildContent)——组件不再渲染Label文本,而是原样渲染你提供的 UI。
2.1 场景一:头像 + 用户名的「用户选择器」
第一个例子利用 Avatar 组件构造了三种头像形态,充分说明自定义内容可以嵌套任意 Ant Design Blazor 组件:
user1:通过Src加载远程图片头像;user2:通过Style指定背景色#f56a00,并以子内容方式显示首字母K(Avatar 的ChildContent优先于Text,见 Avatar.razor.cs);user3:通过Icon="user"显示内置图标头像,背景色#87d068。
三者与下方的User 1/2/3文字组合成完整的分段项内容。切换分段时,Blazor 将选中项的Value("user1"/"user2"/"user3")暴露给回调,业务层据此切换右侧用户详情。
2.2 场景二:双行信息的「季节选择器」
第二个例子展示了纯文本布局的自定义:每个分段项内部放置两个<div>,第一行是季节名(Spring/Summer/Autumn/Winter),第二行是对应的月份区间(Jan-Mar 等)。这种「标题 + 副标题」的双行结构是自定义渲染最常见的落地形态——仅靠Labels参数的单行文本无法实现。
三、源码级原理:ChildContent 的渲染优先级与值绑定
3.1 三段式渲染优先级
SegmentedItem的渲染逻辑定义在 SegmentedItem.razor 中:
<label class="@ClassMapper.Class" style="@Style" id="@Id" @ref="Ref"> <input class="ant-segmented-item-input" type="radio" checked="@_selected"> <div class="ant-segmented-item-label" title="@Label" @onclick="OnClick"> @if (ChildContent != null) { @ChildContent } else if (Icon != null) { <Icon Type="@Icon" /> if (Label != null) { <span>@Label</span> } } else { @Label } </div> </label>从中可以提炼出每个分段项内容的三级渲染优先级:
| 优先级 | 条件 | 渲染内容 |
|---|---|---|
| 1 | ChildContent != null | 渲染自定义内容(本篇文章的核心) |
| 2 | Icon != null | 渲染图标 +Label文本 |
| 3 | 以上均未提供 | 仅渲染Label文本 |
因此,只要SegmentedItem标签内有内容(无论多简单),Icon与Label都会被忽略——这与演示 WithIcon.razor(Icon+Label组合)和 IconOnly.razor(仅ChildContent包一个<Icon>)形成了互补:后两者属于「非自定义内容」路径,而 Custom 演示则完全走ChildContent分支。
3.2 顶层组件的三种数据源优先级
与SegmentedItem呼应,Segmented顶层组件也定义了内容来源的优先级,见 Segmented.razor:
ChildContent(手动书写的SegmentedItem列表)——优先级最高,Custom 演示即此路径;Options(IEnumerable<SegmentedOption<TValue>>数据化配置);Labels(纯字符串数组,Label与Value相同)。
<CascadingValue Value="this" IsFixed> @if (ChildContent != null) { @ChildContent } else if (Options?.Any() == true) { @foreach (var option in Options) { <SegmentedItem TValue="TValue" Label="@option.Label" Value="@option.Value" @key="option.Value" Disabled="@(option.Disabled || Disabled)" /> } } else if (Labels?.Any() == true) { @foreach (var label in Labels) { <SegmentedItem TValue="TValue" Label="@label.ToString()" Value="@label" @key="label" Disabled="Disabled" /> } } </CascadingValue>关键点在于:Segmented通过<CascadingValue Value="this" IsFixed>把自身级联给所有SegmentedItem子组件,子组件在OnInitialized中调用Parent?.AddItem(this)注册自己(见 SegmentedItem.razor.cs)。因此无论内容如何自定义,项目的注册、选中状态管理、切换逻辑始终统一由父组件驱动。
3.3 值绑定与切换行为
自定义内容并不影响值绑定。Segmented<TValue>的核心参数定义在 Segmented.razor.cs 中:
Value/ValueChanged:支持@bind-Value双向绑定;OnChange:选中变化回调,参数为当前选中项的TValue;DefaultValue:默认选中值;Block:将宽度调整为父元素宽度(boolean,默认false);Disabled:整组禁用;SegmentedItem自身也有Disabled可单独禁用某一项;Size:SegmentedSize.Large/ 默认 /SegmentedSize.Small(枚举定义见 SegmentedSize.cs)。
点击流程可概括为:SegmentedItem.OnClick()(禁用时直接返回)→Parent.Select(this)→ 取消旧选中项、写入_value、触发OnChange与ValueChanged、播放滑动滑块动画(见 Segmented.razor.cs 的Select方法与ThumbAnimation)。这意味着:自定义渲染的分段项与普通文本分段项共享同一套选中与动画逻辑,体验完全一致。
完整的组件 API 参数表可参考组件文档 index.zh-CN.md。
四、实际接入:在页面中使用自定义渲染
4.1 基础接入步骤
- 在页面顶部引入命名空间(通常为
@using AntDesign,项目级_Imports.razor一般已全局导入); - 声明
<Segmented TValue="string">,明确泛型类型; - 在每个
<SegmentedItem>内书写自定义 UI,并确保Value唯一; - 按需绑定
@bind-Value或使用OnChange响应切换。
一个可直接运行的最小示例(含双向绑定与回调):
<Segmented TValue="string" @bind-Value="_view" OnChange="OnViewChanged"> <SegmentedItem Value="card"> <div>卡片视图</div> <small>Card</small> </SegmentedItem> <SegmentedItem Value="list"> <div>列表视图</div> <small>List</small> </SegmentedItem> </Segmented> @code { private string _view = "card"; private void OnViewChanged(string value) { // 依据 value 切换右侧内容区域 } }4.2 与数据驱动方式的对比选择
除了ChildContent手动渲染,Segmented还支持两种数据驱动方式,可根据场景选用:
Options:IEnumerable<SegmentedOption<TValue>>,其中SegmentedOption是 record struct(见 SegmentedOption.cs),包含Value、Label、Disabled三个成员,适合选项由后端动态下发、需要单项禁用的场景;Labels:string[]等字符串集合,Label同时充当Value,最简洁,适合纯文本快速布局(见 Basic.razor)。
自定义渲染(ChildContent)适用于 UI 复杂度高、需要内嵌组件或结构化信息的场景;若只是简单的动态字符串列表,优先使用Labels/Options以避免冗余。当三者同时提供时,ChildContent优先于Options优先于Labels(见 Segmented.razor.cs 的参数注释)。
4.3 动态增删分段项
自定义渲染同样支持动态变化:Segmented在OnAfterRenderAsync中检测_optionsChanged,重新计算选中值并刷新滑块位置(见 Segmented.razor.cs)。动态加载更多选项的参考实现见 Dynamic.razor。
五、测试验证:自定义内容不会破坏组件结构
仓库为 Segmented 提供了基于 bUnit 的单元测试,见 SegmentedTests.razor。测试断言了渲染出的 DOM 结构:外层div.ant-segmented包裹div.ant-segmented-group,每个SegmentedItem渲染为<label class="ant-segmented-item...">,内含input[type=radio]与div.ant-segmented-item-label,选中项带有ant-segmented-item-selected类且 radio 带checked属性。
从测试与源码可以确认:无论分段项内容是纯文本、图标还是任意自定义 UI,最终都包在ant-segmented-item-label容器内。这保证了自定义渲染不会破坏组件原有的样式体系与无障碍语义(每个选项仍是真实的 radio 控件)。测试还针对@bind-Value验证了受控值的选中映射(Renders_segmented_with_options用例),说明自定义内容项与绑定逻辑的兼容性。
六、常见问题与注意事项
Value必须唯一且类型一致:多个SegmentedItem若Value相同,选中判定(_items.FirstOrDefault(x => x.Value.Equals(value)))会定位到错误项,导致切换异常。TValue泛型要显式声明:Segmented与SegmentedItem均需指定相同泛型参数;多子项写法下建议显式写出TValue="string",避免类型推断歧义。- 内容与值分离:自定义 UI 仅是「显示层」,
Value才是业务逻辑的标识;不要在 UI 内硬编码业务判断,统一读取回调参数。 - 禁用语义:整组禁用用
Segmented Disabled,单项禁用用SegmentedItem Disabled;自定义内容不会影响禁用判断(见 SegmentedItem.razor.cs 的点击拦截逻辑)。 - 样式隔离:演示中每个自定义项内部使用内联
style或额外 CSS 控制布局;在正式项目中建议为自定义内容补充样式类,避免依赖组件默认间距。
七、小结
Segmented 的自定义渲染能力,本质上是「数据层(Value)与视图层(ChildContent)分离」的设计:SegmentedItem的ChildContent让你可以像写普通 Razor 组件一样自由组合头像、图标、多行文本乃至任意 Blazor 组件,而选中状态、切换回调、滑块动画与禁用逻辑全部由Segmented父组件统一接管。配合Options/Labels两种数据驱动方式与@bind-Value双向绑定,Segmented 可以覆盖从纯文本到富内容的所有分段选择场景。
相关资源:组件 API 文档见 index.zh-CN.md;实现源码见 Segmented.razor、Segmented.razor.cs、SegmentedItem.razor;更多演示见 Segmented 演示目录(Basic、WithIcon、IconOnly、Dynamic、Controlled、Size 等);单元测试见 SegmentedTests.razor。
- UI组件
- 前端
【免费下载链接】ant-design-blazor
🌈A rich set of enterprise-class UI components based on Ant Design and Blazor.
相关推荐
ng-zorro-antd Segmented 自定义渲染实战:基于 `label[nz-segmented-item]` 内容投影打造富交互分段控制器
ng zorro antd Segmented 自定义渲染实战:基于 label nz segmented item 内容投影打造富交互分段控制器 导读 Seg
UI组件前端Ant Design Blazor表格组件分组标题自定义渲染方案
Ant Design Blazor表格组件分组标题自定义渲染方案 痛点场景:数据分组展示的个性化需求 在企业级应用开发中,数据表格的分组展示是常见需求。但默认的
前端UI组件设计系统Ant Design Blazor CheckboxGroup 混合模式 MixedMode 详解:Options 与 ChildContent 的渲染顺序控制
Ant Design Blazor CheckboxGroup 混合模式 MixedMode 详解:Options 与 ChildContent 的渲染顺序控制
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考