virtualize 组件比普通 foreach 快,因为它只渲染可视区域及缓冲区内的条目并复用 dom,而 foreach 会为全部数据生成 dom 导致卡顿;要求 items 为 ilist 或支持 getitemsasync 的分页服务,且需指定 itemsize。

Virtualize 组件为什么比普通 foreach 快
因为 Virtualize 不会一次性渲染全部数据项,只渲染当前可视区域(加少量缓冲)的条目,滚动时动态复用 DOM 元素。而 @foreach 会为每个数据项生成完整 DOM 节点,10 万条数据直接卡死浏览器。
关键前提是:数据源必须支持按索引随机访问(IList<t></t> 或实现 GetItemAsync 的分页服务),不能是纯 IEnumerable<t></t> 流式数据。
基础用法:绑定 IList 并设置 itemSize
最常用场景是本地内存列表,比如 List<product></product>。必须显式指定 itemSize(像素高度),否则无法计算可视范围:
<Virtualize Items="@products" ItemSize="48">
<ItemContent>
<div class="product-item">@context.Name - @context.Price</div>
</ItemContent>
</Virtualize>-
Items必须是IList<t></t>,传products.AsEnumerable()会编译通过但运行时报InvalidOperationException: Items must be an IList<t></t> -
ItemSize是估算值,不精确会导致滚动抖动或空白;若行高不固定,需改用GroupItemSize+ 自定义逻辑,或放弃虚拟化 - 组件内部会调用
Count和indexer,所以Count应为 O(1),避免每次取长度都遍历
异步加载:用 GetItemsAsync 接口对接后端分页
当数据量极大或需服务端过滤时,用 GetItemsAsync 替代 Items,由 Blazor 按需拉取片段:
<Virtualize TItem="Order"
ItemsProvider="@LoadOrders"
ItemSize="60">
<ItemContent>
<div>#@context.OrderId - @context.Status</div>
</ItemContent>
</Virtualize>LoadOrders 方法签名必须为:Func<itemsproviderrequest valuetask>>></itemsproviderrequest>
- 每次滚动触发请求时,
ItemsProviderRequest包含StartIndex和Count,你需据此查数据库(如OFFSET @start ROWS FETCH NEXT @count ROWS ONLY) - 返回的
ItemsProviderResult<titem></titem>必须包含Items和HasMoreItems;漏设HasMoreItems = false会导致无限加载假象 - 注意并发:快速滚动可能触发多个请求,建议在
LoadOrders内做防抖或取消前序CancellationToken
常见卡顿和空白问题排查
虚拟化失效往往不是代码写错,而是隐性约束被打破:
- 父容器未设固定高度或
max-height,Virtualize无法判断可视区域 → 页面必须给外层<div style="height: 500px"> 包裹<li>子元素用了 <code>position: absolute或transform,破坏了默认的流式布局尺寸计算 → 避免在ItemContent内做绝对定位 - 过度使用
@key或复杂组件状态(如内嵌EditForm),导致重渲染开销上升 → 单个ItemContent内保持轻量,表单操作建议抽离到弹窗 - Chrome DevTools 中看到大量
blazor-error-ui提示“JS interop timeout” → 检查GetItemsAsync是否阻塞或超时,服务端响应应控制在 200ms 内
真正难调的是混合场景:既要服务端分页,又要客户端搜索过滤。这时候得自己实现带缓存的 ItemsProvider,把已拉取的片段存在内存里,避免重复请求 —— 这部分没有现成组件,得手写逻辑。








