mcpField
R
react-virtuoso

react-virtuoso

github.com/virtuoso-dev开发1213 次安装2 天前更新
代码仓库

关于 react-virtuoso

react-virtuoso 是用于 React 的虚拟列表、网格和表格组件库,只渲染可见内容,并通过 ResizeObserver 自动测量项目尺寸。适合需要处理大型数据集且项目高度可变的界面。

主要功能

  1. 01虚拟化列表、分组列表、表格和网格
  2. 02自动测量可变项目尺寸
  3. 03支持无限滚动与向前插入
  4. 04提供编程滚动和状态恢复
  5. 05支持窗口滚动及自定义滚动容器

适用场景

  1. 01渲染包含大量条目的列表或数据表
  2. 02实现带粘性分组标题的列表
  3. 03构建响应式多列虚拟网格
  4. 04在加载旧条目时保持列表滚动位置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/virtuoso-dev/skills --skill react-virtuoso

安装后在 Claude Code 中输入/react-virtuoso确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/1.virtuoso/
auto-resizing.md
basic-usage.md
custom-scroll-container.md
customize-rendering.md
endless-scrolling.md
footer.md
horizontal-mode.md
initial-index.md
keyboard-navigation.md
press-to-load-more.md
range-change-callback.md
scroll-handling.md
scroll-seek-placeholders.md
scroll-to-index.md
tailwind-test.md
top-items.md
window-scrolling.md
references/2.grouped-virtuoso/
grouped-by-first-letter.md
grouped-numbers.md
grouped-with-load-on-demand.md
scroll-to-group.md
references/3.virtuoso-grid/
grid-responsive-columns.md
references/4.table-virtuoso/
basic-table.md
table-fixed-columns.md
table-fixed-headers.md
table-grouped.md
references/5.third-party-integration/
material-ui-endless-scrolling.md
mocking-in-tests.md
mui-table-virtual-scroll.md
tanstack-table-integration.md

详细介绍

react-virtuoso

react-virtuoso 只渲染大型列表、网格和表格中可见的部分。它使用 ResizeObserver 自动测量项目大小——可变项目高度开箱即用,无需配置尺寸。

import { Virtuoso } from 'react-virtuoso'
;<Virtuoso style={{ height: '100%' }} data={users} itemContent={(index, user) => <div>{user.name}</div>} />

选择合适的组件

需求使用
平面列表,项目高度可变或固定Virtuoso
带有粘性分组标题的分组GroupedVirtuoso
对行进行虚拟化的 HTML 表格TableVirtuoso
带有分组行和粘性分组标题的表格GroupedTableVirtuoso
响应式多列网格中的等尺寸项目VirtuosoGrid
聊天 / AI 对话 UI(流式输出、贴底、前置历史记录)@virtuoso.dev/message-list — 改用 message-list 技能
带有列、排序、筛选和列功能的数据网格@virtuoso.dev/data-table — 改用 data-table 技能

所有组件共享相同的核心属性(data/totalCount、itemContent、components、滚动回调、ref 方法)。

无需做的事情

与 TanStack Virtual 或 react-window 不同,react-virtuoso 会自行测量项目。不要照搬这些库的模式:

  • 无需 estimateSize、measureElement 或 data-index 配置——测量会自动进行。
  • 不要在项目上使用绝对定位或 transform: translateY——库会为项目定位。
  • 无需固定的 itemSize——默认支持可变高度。只有在项目确实高度统一时,才传入 fixedItemHeight 作为性能优化。
  • 无需计算窗口化逻辑——传入 data(或 totalCount),并在 itemContent 中渲染项目即可。

核心规则

  • 组件需要设置高度。 设置 style={{ height: '100%' }}(父元素需有明确高度)或固定高度。高度为零的容器不会渲染任何内容。
  • 不要在项目上设置 CSS 外边距。 ResizeObserver 报告的 contentRect 不包含外边距,因此计算出的总高度会偏小——典型表现是列表无法滚动到底部。请改用内边距。注意 <p>、标题、<ul>、<blockquote>、<pre> 的默认外边距。
  • 有项目时使用 data,不要使用 totalCount。 使用 data 时,itemContent={(index, item) => ...} 会接收项目。只有在项目由索引派生时才使用 totalCount。更新时必须生成新的数组引用。
  • 只要列表可能进行前置添加、重新排序或筛选,就应提供 computeItemKey={(index, item) => item.id}。 默认键为索引,位置变化时会重新挂载项目(导致状态丢失)。
  • 在渲染函数外定义 components 覆盖项。 内联定义会在每次渲染时创建新的组件类型,导致每次滚动都重新挂载子树。Scroller 和 List 覆盖项必须将 ref 转发到其 DOM 元素。
  • 项目内容不能渲染高度为零的元素。 报错 "zero-sized element, this should not happen" 表示某个项目测得高度为 0px——请从数据中筛除空项目。

常见模式

无限滚动
<Virtuoso data={items} endReached={() => loadMore()} itemContent={(index, item) => <Item item={item} />} />

endReached 会在滚动到底部时触发;通过 components.Footer 渲染加载指示器。若要点击“加载更多”,请将按钮放在 Footer 中。

前置添加项目(反向无限滚动)

直接前置添加会导致列表跳动。请改为维护 firstItemIndex,并将其减去前置添加的项目数:

const [firstItemIndex, setFirstItemIndex] = useState(START)

const prepend = async () => {
  const older = await fetchOlderItems()
  setFirstItemIndex((i) => i - older.length)
  setItems((current) => [...older, ...current])
}

<Virtuoso
  computeItemKey={(_, item) => item.id}
  data={items}
  firstItemIndex={firstItemIndex}
  initialTopMostItemIndex={{ index: 'LAST' }}
  startReached={() => void prepend()}
  ...
/>

firstItemIndex 必须保持为正数,因此初始值要设得较大(例如 100000)。对于 GroupedVirtuoso,只减去新项目的数量,不包括分组标题。请参阅 endless-scrolling。

跟随新项目(日志、信息流)
<Virtuoso followOutput="smooth" data={messages} ... />

只有当用户已位于底部时,followOutput 才会滚动到新增的底部项目。它接受 'auto' | 'smooth' | false,也可以接受函数 (isAtBottom) => ... 来实现自定义逻辑。对于完整的聊天界面,建议使用 @virtuoso.dev/message-list。

以编程方式滚动
const ref = useRef<VirtuosoHandle>(null)
ref.current?.scrollToIndex({ index: 500, align: 'center', behavior: 'smooth' })

句柄上还提供:scrollIntoView(仅在目标不可见时滚动,适用于键盘导航)、scrollTo/scrollBy(基于像素滚动)以及 getState(在重新挂载时用于 restoreStateFrom 的快照,例如返回导航)。

要从某个项目开始显示,请使用 initialTopMostItemIndex={{ index, align: 'start' }},不要使用 initialScrollTop,后者会先从顶部渲染,然后再跳转。

分组列表
<GroupedVirtuoso
  groupCounts={[20, 30]} // items per group, in order
  groupContent={(groupIndex) => <Header group={groups[groupIndex]} />}
  itemContent={(index, groupIndex) => <Item item={items[index]} />} // index is absolute across all items
/>

你需要提供扁平数据和 groupCounts,并自行将索引映射回数据。

表格
<TableVirtuoso
  data={rows}
  fixedHeaderContent={() => (
    <tr>
      <th>Name</th>
    </tr>
  )}
  itemContent={(index, row) => (
    <>
      <td>{row.name}</td>
    </>
  )} // <td> cells only — the row <tr> is rendered for you
/>

不要在表格上设置 border-collapse: collapse,否则粘性表头的边框会随表格主体一起滚动。请使用 border-collapse: separate 并显式设置单元格边框。可通过 components(Table、TableRow、TableHead、TableBody)自定义结构。

网格

VirtuosoGrid 会按列虚拟化尺寸相同的项目。你可以通过 CSS 控制列数:为 components.Item 设置百分比宽度(三列时为 33%,可通过媒体查询调整),并为 components.List 设置 display: flex; flex-wrap: wrap。请参阅 grid-responsive-columns。

页面级滚动

使用 useWindowScroll 让列表跟随文档滚动,或使用 customScrollParent={element} 将其附加到现有的可滚动祖先元素。只能选择一种滚动模式,同时使用会导致两个容器都发生滚动。

测试

JSDOM 不支持布局,因此如果不模拟测量值,Jest/Vitest 中的项目将不会渲染:

render(<Virtuoso data={data} />, {
  wrapper: ({ children }) => (
    <VirtuosoMockContext.Provider value={{ viewportHeight: 300, itemHeight: 100 }}>{children}</VirtuosoMockContext.Provider>
  ),
})

对于网格,请使用 VirtuosoGridMockContext(添加 viewportWidth、itemWidth)。滚动行为测试优先使用真实浏览器(Playwright)。

故障排查

症状可能原因解决方法
没有任何内容渲染容器高度为零为组件或其父元素设置实际高度
无法滚动到最后几项/滚动位置跳到接近底部项目内容使用了外边距将外边距替换为内边距
滚动时项目闪烁或丢失状态内联的 components 定义或基于索引的键将组件提取到外部定义;添加 computeItemKey
在前置插入项目时列表跳动添加项目时未调整 firstItemIndex使用 firstItemIndex 前置插入模式
“zero-sized element, this should not happen”渲染的项目高度为零从数据中筛除空项目
开发环境中出现 “ResizeObserver loop completed with undelivered notifications” 覆盖层无害的 ResizeObserver 时序问题,由开发服务器覆盖层显示出来在 webpack/vite 覆盖层配置中禁用 runtimeErrors;在错误跟踪中安全过滤
难以解释的尺寸行为—设置 logLevel={LogLevel.DEBUG},并在启用所有级别的情况下查看控制台

调优:increaseViewportBy 会在视口外额外渲染一些像素(更流畅,但 DOM 更多);defaultItemHeight 会跳过初始探测渲染;fixedItemHeight 会完全跳过测量(仅适用于所有项目高度一致的情况);scrollSeekConfiguration 会在快速滚动期间将项目替换为占位符。

参考资料

完整代码详解指南见 references/:

完整 API 参考文档:https://virtuoso.dev/react-virtuoso/

评论

支持 Markdown