- UI组件
- 前端
【免费下载链接】ant-design-blazor
🌈A rich set of enterprise-class UI components based on Ant Design and Blazor.
ant-design-blazor 的Select组件在初始化DataSource数据项时,会通过反射(更准确地说,是基于PathHelper生成表达式委托)一次性提取每一条数据的 Label(标签)、Value(值)、GroupName(分组名)与 Disabled(禁用)状态,并以SelectOptionItem的形式缓存为内部选项。为了在每次渲染周期避免重复执行这一开销,组件提供了IgnoreItemChanges参数:默认值为True(忽略变更,性能最优);当你希望在运行时动态更新标签、分组或禁用状态时,则将其设为False。读完本文,你将掌握该参数的完整语义、底层实现原理、演示用例以及何时开启/关闭的取舍策略。
一、背景:Select 如何"初始化"一条 Item
Select<TItemValue, TItem>是一个泛型组件,它既可以接收子元素形式的选项,也可以通过DataSource绑定一个数据集合。当使用DataSource时,组件需要把数据对象转换成内部可渲染的SelectOptionItem<TItemValue, TItem>,转换过程发生在Select.razor.cs的CreateDeleteSelectOptions()方法中(实现位置):
var disabled = false; var groupName = string.Empty; var label = _getLabel == null ? GetLabel(item) : _getLabel(item); ... if (_getDisabled != default) disabled = _getDisabled(item); if (!string.IsNullOrWhiteSpace(GroupName)) groupName = _getGroup(item);其中_getLabel、_getValue、_getDisabled、_getGroup这四个委托就是"初始化"的核心:它们由字符串属性名(LabelName、ValueName、DisabledName、GroupName)通过PathHelper.GetDelegate<TItem, bool>(value)之类的方法编译而成(Select.razor.cs#L142-L172)。也就是说,LabelName="@nameof(Person.Name)"并不会在每次渲染时都用反射去查找属性,而是在参数 Setter 中一次性生成一个强类型委托并缓存:
- PathHelper.cs 内部使用
ConcurrentDictionary对表达式、Lambda 与 Delegate 做了多级缓存; - 运行时只需调用已编译的委托
_getLabel(item),即可拿到标签字符串。
这一机制是理解IgnoreItemChanges的前提:"通过反射设置"发生在选项创建时,且只发生一次。原文档(TrackItemChanges.md)中描述的"label value (e.g. Lucy)、group name、disabled value 由反射设置",指的就是这条初始化链路。
二、IgnoreItemChanges 参数:语义与默认值
IgnoreItemChanges定义在 Select.razor.cs#L177-L183:
/// <summary> /// Is used to increase the speed. If you expect changes to the label name, /// group name or disabled indicator, disable this property. /// </summary> /// <default value="true"/> [Parameter] public bool IgnoreItemChanges { get; set; } = true;参数说明(出自 Select 文档参数表):
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| IgnoreItemChanges | 用于提高速度。如果希望更改标签名称、组名称或禁用指示器,请禁用此属性。 | bool | true |
语义可以概括为一句话:
IgnoreItemChanges = true(默认):组件假定DataSource中的对象引用集合在运行期间不会发生"内容变化",渲染时只做增量增删,不复核每个选项的 Label/GroupName/Disabled 是否已过时;IgnoreItemChanges = false:组件在数据源变化时对已有选项的 Label、GroupName、Disabled 等字段做整体刷新,从而让运行时的字段修改能够反映到界面上,代价是更多的重建工作。
与 OnDataSourceChanged 的区别
容易混淆的一点是:IgnoreItemChanges关注的是对象内部字段变化,而OnDataSourceChanged回调(Select.razor.cs#L210-L215)关注的是数据源引用/集合本身的变化——它的注释明确写着"仅在数据源对象/引用变化时触发,数据源内部某个值变化不会触发"。因此,若只替换整个DataSource列表而对象字段不变,用默认配置即可;若要修改列表内某个对象的Name等字段并期望界面跟随,就必须配合IgnoreItemChanges="false"。
三、底层原理:CreateDeleteSelectOptions 的分支逻辑
在CreateDeleteSelectOptions()中,IgnoreItemChanges直接影响两条关键路径(Select.razor.cs#L631-L769):
路径一:清理已不存在的选项
var exists = _datasource.FirstOrDefault(x => x.Equals(selectOption.Item)); if (exists is null) { if (IgnoreItemChanges) { SelectOptionItems.RemoveAt(i); } RemoveEqualityToNoValue(selectOption); ... }路径二:决定是否重建/刷新全部选项
if (!IgnoreItemChanges) { SelectOptionItems.Clear(); } ... else if (exists && !IgnoreItemChanges) { updateSelectOption.Label = label; updateSelectOption.IsDisabled = disabled; updateSelectOption.GroupName = groupName; updateSelectOption.IsHidden = isSelected && HideSelected; SelectOptionItems.Add(updateSelectOption); ... }结合EvaluateDataSourceChange()(Select.razor.cs#L467-L535)可以看到完整的数据变更检测链路:
- 组件在
OnParametersSetAsync()中首先调用EvaluateDataSourceChange(),用DataSource.SequenceEqual(...)结合浅拷贝副本_dataSourceShallowCopy与DataSourceEqualityComparer判断"集合是否真的变了"; - 若判定
_dataSourceHasChanged,则进入CreateDeleteSelectOptions(); - 此时若
IgnoreItemChanges == false,旧选项会被清空并按最新数据逐条重建(SelectOptionItems.Clear()),从而把对象最新的字段值写入Label、GroupName、IsDisabled; - 若为
true,则只做增量增删(删除消失的项、追加新增的项),已存在的选项保持原样,运行时的字段修改不会被同步。
从源码结构可以推断:true模式下每次数据变更的开销更小(避免反复为每个选项执行取值委托与重建对象),而false模式以重建为代价换取"标签/分组/禁用状态跟随对象字段实时变化"的能力。
四、实战演示:运行期修改标签名称
官方演示 TrackItemChanges.razor 完整展示了IgnoreItemChanges="false"的典型用法:界面上提供一个 "Rename Lucy" 按钮,点击后修改_persons中 Id=2 的Name,让下拉框与选中项即时反映新名称。
<Select TItem="Person" TItemValue="int?" DataSource="@_persons" @bind-Value="@_selectedValue" LabelName="@nameof(Person.Name)" ValueName="@nameof(Person.Id)" DisabledName="@nameof(Person.NotAvailable)" Style="width: 200px" DefaultValue="2" Placeholder="Select a person" DefaultActiveFirstOption IgnoreItemChanges="false" OnSelectedItemChanged="OnSelectedItemChangedHandler" AllowClear> </Select> <Button OnClick="@RenameLabel" >Rename Lucy</Button> <br /><br /> <p> Selected Value: @_selectedValue <br/> Selected Item: @_selectedItem?.Name </p> @code { class Person { public Person(){} public Person(Person obj) { Id = obj.Id; Name = obj.Name; NotAvailable = obj.NotAvailable; } public int Id { get; set; } public string Name { get; set; } public bool NotAvailable { get; set; } } List<Person> _persons; int? _selectedValue; Person _selectedItem; protected override void OnInitialized() { _persons = new List<Person> { new Person {Id = 1, Name = "Jack"}, new Person {Id = 2, Name = "Lucy"}, new Person {Id = 3, Name = "Yaoming"}, new Person {Id = 4, Name = "Frieda"}, new Person {Id = 5, Name = "Kathy", NotAvailable = true}, new Person {Id = 6, Name = "Kate"}, new Person {Id = 7, Name = "Eric"} }; } private void RenameLabel() { var person = _persons.First(x => x.Id == 2); if (person.Name.Equals("Lucy", StringComparison.InvariantCultureIgnoreCase)) { person.Name = "Lucie"; } else { person.Name = "Lucy"; } } private void OnSelectedItemChangedHandler(Person value) { _selectedItem = value; } }关键点拆解:
LabelName、ValueName、DisabledName分别指向Person的Name、Id、NotAvailable属性,三者即前文所述"由反射初始化"的三类字段(另可加GroupName对应分组名);IgnoreItemChanges="false"是本演示的灵魂:点击 "Rename Lucy" 后_persons[1].Name由Lucy变为Lucie,若不关闭该参数,下拉框和选中项将始终显示旧的Lucy;AllowClear与DefaultValue="2"配合,演示了默认选中与清除行为在数据刷新场景下的正确表现。
注意演示的Person类额外提供了一个拷贝构造函数Person(Person obj),这与底层EvaluateDataSourceChange()中"获取数据源项浅拷贝方法"(GetDataSourceItemCloneMethod(),Select.razor.cs#L70-L93)相呼应:组件会对DataSource做一次浅拷贝快照用于后续的集合相等性比较,带拷贝构造函数的模型类能获得更准确的变更检测。
五、测试佐证:运行时字段变更确实能被感知
仓库测试 Select.OnDataSourceChange.Tests.razor 中用 bUnit 覆盖了与该参数相关的核心场景:
React_to_label_change(L258-L282):以IgnoreItemChanges="false"渲染 Select,随后把_personsClass[1].Name改为"Lucie"并重新设置DataSource,断言选中项文本等于新名称"Lucie",且OnDataSourceChanged被触发;Object_DataSource_change_replace_all_with_right_order(L358-L397)与Object_DataSource_change_replace_some_with_right_order(L401+):在IgnoreItemChanges="false"下整体替换或部分替换数据源,断言选项数量、顺序、选中状态均正确重建。
这些用例从测试层面印证了:当IgnoreItemChanges="false"时,对象字段修改 + 数据源重设会触发选项内容的完整刷新;而源码中if (exists is null) { if (IgnoreItemChanges) { ...RemoveAt(i); } }等分支则说明默认true模式下组件只做增量维护。
六、如何选择:性能与实时性的取舍
| 场景 | 推荐配置 | 原因 |
|---|---|---|
| 数据源初始化后不再修改对象字段(最常见的只读列表场景) | 保持默认true | 避免每次渲染周期重复取值与重建选项,性能最优 |
| 运行期需要修改对象的 Label/分组名/禁用状态并即时刷新界面 | IgnoreItemChanges="false" | 数据源变化时会清空并重建选项,保证界面与对象字段一致 |
| 数据源集合频繁整体替换(增删项、换顺序) | 默认true即可 | 增量增删已能正确同步集合变化,无需重建全部选项 |
| 大数据量列表 + 高频渲染 | 保持默认true | false会带来额外的清空与逐项重建开销 |
补充两点实践建议:
- 无论是
true还是false,修改对象字段后都需要让组件感知到数据源"变了"——对于引用类型数据源,重新给DataSource赋值(或触发一次参数重设)是必要的,因为组件本身不会监听对象内部属性的变化; - 若你的模型类有可用的拷贝构造函数,组件会用它生成浅拷贝快照参与集合比较(见 GetDataSourceItemCloneMethod),变更检测更可靠;没有时也能通过默认路径工作,只是精度与开销不同。
总结
IgnoreItemChanges是 ant-design-blazorSelect组件在"渲染性能"与"数据实时性"之间提供的一个开关:默认true让组件以增量方式维护选项、避免反复执行由PathHelper编译出的取值委托;设为false则让组件在数据源变化时重建选项,从而支持运行时修改标签(Label)、分组名(GroupName)与禁用状态(Disabled)。理解CreateDeleteSelectOptions()中exists is null与exists && !IgnoreItemChanges两条分支,就能清楚把握该参数的底层行为,并结合官方演示与测试用例,在自己的业务场景中做出正确的性能取舍。
- UI组件
- 前端
【免费下载链接】ant-design-blazor
🌈A rich set of enterprise-class UI components based on Ant Design and Blazor.
相关推荐
Ant Design Blazor Select 组件性能优化:IgnoreItemChanges 参数深度解析与实战
Ant Design Blazor Select 组件性能优化:IgnoreItemChanges 参数深度解析与实战 导读 在 ant design blaz
前端UI组件设计系统Civitai 许可费与付费访问重构指南:从分级价格上限到月度定价额度(PricingSlot)机制全解析
Civitai 许可费与付费访问重构指南:从分级价格上限到月度定价额度(PricingSlot)机制全解析 导读 本文基于仓库内 docs/licensing
UI组件前端Blazor WebAssembly性能优化:ant-design-blazor最佳实践
Blazor WebAssembly性能优化:ant design blazor最佳实践 引言:从10秒到1秒的加载革命 你是否也曾遭遇Blazor WebAs
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考