Skip to content

feat: add virtual scroll support for Bubble.List and Conversations - #1979

Open
wenzeyu8888-rgb wants to merge 4 commits into
ant-design:mainfrom
wenzeyu8888-rgb:feat/virtual-scroll
Open

feat: add virtual scroll support for Bubble.List and Conversations#1979
wenzeyu8888-rgb wants to merge 4 commits into
ant-design:mainfrom
wenzeyu8888-rgb:feat/virtual-scroll

Conversation

@wenzeyu8888-rgb

@wenzeyu8888-rgb wenzeyu8888-rgb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Changes

  • Add virtual prop to BubbleList and Conversations (default: false)
  • Use @rc-component/virtual-list for virtual scrolling
  • Support dynamic height via forwardRef on ConversationsItem and GroupTitle
  • Support autoScroll to bottom in virtual mode for BubbleList
  • Add virtual-list demos for both components
  • Add unit tests (33 tests) and benchmark tests (1000+ items)
  • Update API docs (zh-CN & en-US)

Related Issue

Closes #1948

Summary by CodeRabbit

  • 新功能
    • Bubble.List 新增虚拟滚动(virtual),支持按 key 定位、置顶/置底与自动滚动,并可在虚拟/普通模式间切换。
    • Conversations 新增虚拟滚动(virtual),在分组与大量数据场景下渲染更高效且保持激活态交互一致。
  • 文档
    • 新增 Bubble/Conversations 虚拟滚动示例,并在 API 中补充 virtual 属性说明。
  • 测试
    • 新增 Bubble 虚拟与大数据基准测试;新增 Conversations 虚拟滚动行为测试。
  • 改动
    • 强化部分列表项/分组标题的 ref 与 DOM 引用能力,并适配虚拟模式渲染结构。

- Add  prop to BubbleList and Conversations (default: false)
- Use @rc-component/virtual-list for virtual scrolling
- Support dynamic height via forwardRef on ConversationsItem and GroupTitle
- Support autoScroll to bottom in virtual mode for BubbleList
- Add virtual-list demos for both components
- Add unit tests (33 tests) and benchmark tests (1000+ items)
- Update API docs (zh-CN & en-US)

Closes ant-design#1948
@dosubot dosubot Bot added enhancement New feature or request javascript Pull requests that update Javascript code labels Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9776e0ab-1a94-4ec8-8bd7-03d7fa481239

📥 Commits

Reviewing files that changed from the base of the PR and between 54bc3ad and 2f3f55e.

📒 Files selected for processing (1)
  • packages/x/components/bubble/BubbleList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/x/components/bubble/BubbleList.tsx

📝 Walkthrough

Walkthrough

Bubble.List 与 Conversations 新增可选虚拟滚动能力,包含动态高度、滚动控制、分组数据处理、ref 支持、单元测试、基准测试、演示和中英文文档更新。

Changes

Bubble.List 虚拟滚动

Layer / File(s) Summary
虚拟渲染与滚动控制
packages/x/components/bubble/interface.ts, packages/x/components/bubble/BubbleList.tsx, packages/x/package.json
新增 virtual 配置、动态高度监听、VirtualList 渲染、自动滚动、滚动定位及依赖。
虚拟模式测试
packages/x/components/bubble/__tests__/list-benchmark.test.tsx, packages/x/components/bubble/__tests__/list-virtual.test.tsx
覆盖大数据渲染、模式切换、滚动调用、角色渲染、样式传递和数据更新。
演示与文档
packages/x/components/bubble/demo/virtual-list.*, packages/x/components/bubble/index.*.md
新增 1000 条消息的虚拟列表演示,并更新示例索引与 API 文档。

Conversations 虚拟滚动

Layer / File(s) Summary
虚拟数据与行渲染
packages/x/components/conversations/index.tsx
新增 virtual 配置、容器高度监听、扁平化分组数据及 VirtualList 行渲染。
虚拟行组件 ref 支持
packages/x/components/conversations/GroupTitle.tsx, packages/x/components/conversations/Item.tsx
GroupTitle 与 ConversationsItem 转发列表项 ref,并调整虚拟模式下的 GroupTitle 包装结构。
虚拟模式测试
packages/x/components/conversations/__tests__/virtual.test.tsx
覆盖分组、激活回调、空数据、大数据、模式切换、ref、禁用项和自定义标题。
演示与文档
packages/x/components/conversations/demo/virtual-list.*, packages/x/components/conversations/index.*.md
新增 1000 条分组会话演示,并更新示例索引与 API 文档。

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Container
  participant BubbleList
  participant VirtualList
  Container->>BubbleList: 设置固定高度与 virtual
  BubbleList->>VirtualList: 传入条目、itemHeight 与 virtualHeight
  BubbleList->>VirtualList: 数据增长或内容变化时滚动到末尾
Loading
sequenceDiagram
  participant Container
  participant Conversations
  participant VirtualList
  participant ConversationsItem
  Container->>Conversations: 设置固定高度与 virtual
  Conversations->>VirtualList: 传入扁平化分组数据
  VirtualList->>ConversationsItem: 渲染会话行
  ConversationsItem-->>Conversations: 返回激活项 key 与数据
Loading

Suggested labels: documentation

Poem

兔耳竖起,虚拟列表开花,
千条消息轻轻落下。
分组会话排成月牙,
滚动到底,尾巴不差。
测试敲鼓,文档起舞,
咕咕一声,代码开跑!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了本次改动:为 Bubble.List 和 Conversations 增加虚拟滚动支持。
Linked Issues check ✅ Passed 实现覆盖了 #1948 要求的可选虚拟滚动、动态高度、自动滚到底部、benchmark、示例、文档和单测。
Out of Scope Changes check ✅ Passed 未发现明显与 #1948 无关的代码改动,附加修改均围绕虚拟滚动支持展开。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces virtual scrolling support to the BubbleList and Conversations components using @rc-component/virtual-list to optimize performance for large datasets. While the feature is a great addition, several critical issues were identified in the review: the virtualized flatData in Conversations does not respect group collapse states, and ref forwarding is broken for group titles because the context provider is used as the root element in the virtual list render function. Additionally, injecting raw <style> tags directly inside the render methods of both components is an anti-pattern and should be refactored into CSS-in-JS style generators.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +305 to +325
const flatData = React.useMemo<FlatItem[]>(() => {
if (!virtualProp) return [];
const result: FlatItem[] = [];
groupList.forEach((groupInfo, groupIndex) => {
if (groupInfo.enableGroup) {
result.push({
type: 'group-title',
key: `__group__${groupInfo.name || groupIndex}`,
groupInfo,
});
}
groupInfo.data.forEach((conversationInfo) => {
result.push({
type: 'item',
key: (conversationInfo as ConversationItemType).key || `__item__${result.length}`,
conversationInfo,
});
});
});
return result;
}, [virtualProp, groupList]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In virtual mode, the list is flattened, but flatData does not take the group collapse state (expandedKeys and enableCollapse) into account. As a result, collapsing a group will not hide its items in the virtual list.\n\nWe should check if the group is collapsed, and if so, skip adding its items to flatData. Also, make sure to add enableCollapse and expandedKeys to the dependency array of useMemo.

    const flatData = React.useMemo<FlatItem[]>(() => {\n      if (!virtualProp) return [];\n      const result: FlatItem[] = [];\n      groupList.forEach((groupInfo, groupIndex) => {\n        const groupName = groupInfo.name || String(groupIndex);\n        const mergeCollapsible = groupInfo.collapsible && enableCollapse;\n        const groupOpen = !mergeCollapsible || !!expandedKeys?.includes?.(groupName);\n\n        if (groupInfo.enableGroup) {\n          result.push({\n            type: 'group-title',\n            key: \"__group__\" + groupName,\n            groupInfo,\n          });\n        }\n        if (groupOpen) {\n          groupInfo.data.forEach((conversationInfo) => {\n            result.push({\n              type: 'item',\n              key: (conversationInfo as ConversationItemType).key || \"__item__\" + result.length,\n              conversationInfo,\n            });\n          });\n        }\n      });\n      return result;\n    }, [virtualProp, groupList, enableCollapse, expandedKeys]);

Comment on lines +119 to 130
type FlatItem =
| {
type: 'group-title';
key: string;
groupInfo: GroupInfoType;
}
| {
type: 'item';
key: string;
conversationInfo: ItemType;
};
const ForwardConversations = React.forwardRef<ConversationsRef, ConversationsProps>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To support proper ref forwarding for group-title items in the virtual list, we should define a helper component VirtualGroupTitle that forwards its ref to GroupTitle while wrapping it with GroupTitleContext.Provider.

type FlatItem =\n  | {\n      type: 'group-title';\n      key: string;\n      groupInfo: GroupInfoType;\n    }\n  | {\n      type: 'item';\n      key: string;\n      conversationInfo: ItemType;\n    };\n\nconst VirtualGroupTitle = React.forwardRef<\n  HTMLLIElement,\n  {\n    prefixCls: string;\n    item: FlatItem & { type: 'group-title' };\n    enableCollapse: boolean;\n    expandedKeys: string[];\n    onItemExpand: any;\n    collapseMotion: any;\n    className?: string;\n  }\n>((props, ref) => {\n  const { prefixCls, item, enableCollapse, expandedKeys, onItemExpand, collapseMotion, className } = props;\n  return (\n    <GroupTitleContext.Provider\n      value={{\n        prefixCls,\n        groupInfo: item.groupInfo,\n        enableCollapse,\n        expandedKeys,\n        onItemExpand,\n        collapseMotion,\n      }}\n    >\n      <GroupTitle ref={ref} className={className} virtual />\n    </GroupTitleContext.Provider>\n  );\n});\nconst ForwardConversations = React.forwardRef<ConversationsRef, ConversationsProps>(

Comment on lines +380 to +397
if (item.type === 'group-title') {
return (
<GroupTitleContext.Provider
value={{
prefixCls,
groupInfo: item.groupInfo,
enableCollapse,
expandedKeys,
onItemExpand,
collapseMotion,
}}
>
<GroupTitle
className={clsx(contextConfig.classNames.group, classNames.group)}
virtual
/>
</GroupTitleContext.Provider>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In VirtualList, the height of each item is measured by attaching a ref to the root element returned by the render function.\nHowever, for group-title items, the root element returned is GroupTitleContext.Provider. Since React Context Providers do not accept or forward refs, the ref attached by rc-virtual-list will be lost (and may trigger React warnings). This will prevent rc-virtual-list from measuring the height of group titles, leading to incorrect scroll positions and rendering glitches.\n\nWe should use the VirtualGroupTitle helper component defined earlier, which correctly forwards the ref to the underlying GroupTitle DOM element.

                if (item.type === 'group-title') {\n                  return (\n                    <VirtualGroupTitle\n                      prefixCls={prefixCls}\n                      item={item}\n                      enableCollapse={enableCollapse}\n                      expandedKeys={expandedKeys}\n                      onItemExpand={onItemExpand}\n                      collapseMotion={collapseMotion}\n                      className={clsx(contextConfig.classNames.group, classNames.group)}\n                    />\n                  );\n                }

Comment on lines +294 to +299
<style>{`
.${listPrefixCls}-scroll-box .rc-virtual-list-scrollbar { display: none !important; }
.${listPrefixCls}-scroll-box .rc-virtual-list-holder {
overflow-y: auto !important;
}
return (
<BubbleListItem
classNames={omit(classNames, ['root', 'scroll'])}
styles={omit(styles, ['root', 'scroll'])}
{...omit(mergedProps, ['key'])}
key={item.key}
_key={item.key}
bubblesRef={bubblesRef}
/>
);
`}</style>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Injecting raw <style> tags directly inside the component's render method is an anti-pattern in React and Ant Design. It can cause SSR hydration mismatches, violate Content Security Policy (CSP) rules regarding inline styles, and bypass the CSS-in-JS theme token system.\n\nThese styles should be moved to the CSS-in-JS style generator file (useBubbleListStyle).

Comment on lines +354 to +368
<style>{`
.${prefixCls} { overflow-y: hidden !important; padding: 0 !important; }
.${prefixCls} .rc-virtual-list-scrollbar { display: none !important; }
.${prefixCls} .rc-virtual-list-holder {
overflow-y: auto !important;
padding: 12px;
box-sizing: border-box;
}
.${prefixCls} .rc-virtual-list-holder-inner > .${prefixCls}-item {
margin-top: 4px;
}
.${prefixCls} .rc-virtual-list-holder-inner > .${prefixCls}-group + .${prefixCls}-item {
margin-top: 0;
}
`}</style>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Injecting raw <style> tags directly inside the component's render method is an anti-pattern in React and Ant Design. It can cause SSR hydration mismatches, violate Content Security Policy (CSP) rules regarding inline styles, and bypass the CSS-in-JS theme token system.\n\nThese styles should be moved to the CSS-in-JS style generator file (useStyle).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/x/components/conversations/index.tsx (1)

340-350: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

从虚拟列表高度中扣除 creation 占用空间。

virtualHeight 是整个根容器的高度,但 Creation 与 VirtualList 是上下排列的兄弟节点。启用 creation 后,两者总高度超过容器,而根节点又被设为 overflow-y: hidden,列表底部将被裁切。请监听实际列表区域,或减去创建按钮的高度。

Also applies to: 369-372

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/conversations/index.tsx` around lines 340 - 350,
调整虚拟列表高度计算,使其在启用 Creation 时扣除 Creation 实际占用的垂直空间。围绕 virtualHeight、Creation 和
VirtualList 的布局逻辑监听实际列表区域或测量创建区域高度,确保两者总高度不超过根容器;未启用 creation 时保持现有高度行为。
🧹 Nitpick comments (2)
packages/x/components/bubble/demo/virtual-list.tsx (1)

18-24: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

统一 React Hooks 的调用风格

示例代码中混用了解构导入的 useState 与直接通过 React.useState / React.useMemo 调用的方式。为了保证文档示例代码风格的一致性和可读性,建议在源码及对应的 Markdown 文档中统一使用解构后的 Hooks 调用。

  • packages/x/components/bubble/demo/virtual-list.tsx#L18-L24: 在文件顶部的 import 中加入 useMemo,并将所有的 React.useStateReact.useMemo 统一修改为 useStateuseMemo
  • packages/x/components/bubble/demo/virtual-list.md#L32-L38: 同步上述修改至 Markdown 中的示例代码块,确保文档和源码保持完全一致。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/bubble/demo/virtual-list.tsx` around lines 18 - 24,
Unify React Hook usage by importing useMemo alongside useState in
virtual-list.tsx and replacing all React.useState and React.useMemo calls with
the destructured hooks. Apply the same changes to the example code in
packages/x/components/bubble/demo/virtual-list.md lines 32-38 so the source and
documentation remain identical.
packages/x/components/bubble/__tests__/list-virtual.test.tsx (1)

17-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

增强对 itemKey 属性类型的兼容性

当前 Mock 实现中,仅将 truthy 的 itemKey 视作函数进行调用。由于 @rc-component/virtual-list 支持 itemKey 作为字符串或函数,如果将来 BubbleList 传递了字符串形式的 itemKey(例如默认的 itemKey="key"),当前代码会抛出 TypeError: itemKey is not a function

建议更新兼容逻辑以提高 Mock 的健壮性。

💡 建议的代码重构
-        {data?.map((item: any, index: number) => (
-          <div key={itemKey ? itemKey(item) : index}>{children(item, index)}</div>
-        ))}
+        {data?.map((item: any, index: number) => {
+          const key = typeof itemKey === 'function' ? itemKey(item) : (itemKey ? item[itemKey as string] : index);
+          return <div key={key}>{children(item, index)}</div>;
+        })}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/bubble/__tests__/list-virtual.test.tsx` around lines 17
- 19, Update the mock list rendering around itemKey to support both function and
string forms accepted by BubbleList: invoke function itemKey values, and resolve
string itemKey values from each item for the React key, with the existing index
fallback when itemKey is absent. Do not call non-function itemKey values as
functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/x/components/bubble/__tests__/list-benchmark.test.tsx`:
- Around line 7-24: Remove the VirtualList mock from the benchmark setup in
list-benchmark.test.tsx so performance measurements use the real
`@rc-component/virtual-list` implementation. Keep mock-based prop/ref assertions
in separate unit tests, and run the benchmark in a browser environment with a
constrained mounted-node count when comparing virtual and non-virtual modes.

In `@packages/x/components/bubble/BubbleList.tsx`:
- Line 149: Update the virtual list ref in BubbleList to use the exported
ListRef type instead of any. In the virtual-mode scrollTo path, map the public
block option to the virtual list’s supported align option and pass only its
supported parameters. Replace key truthiness checks with nullish checks so valid
0 and empty-string keys are preserved.
- Around line 211-223: Update the virtual auto-scroll effect around
prevItemsLengthRef so it also responds when existing message content grows, not
only when mergedItems.length increases. Track or reuse the virtual list’s
content-size change signal and scroll to the final item while autoScroll remains
enabled, preserving the existing behavior for newly added items and avoiding
scrolling on removals.

In `@packages/x/components/conversations/__tests__/virtual.test.tsx`:
- Around line 6-24: 在 conversations 虚拟列表测试中补充至少一个不 mock
`@rc-component/virtual-list` 的真实 VirtualList 测试,使用 1000 条数据验证仅渲染可视窗口而非全部行,并记录可用于检测
DOM 节点数量或渲染性能退化的基准指标;保留现有 mock 测试用于基础断言。

In `@packages/x/components/conversations/index.tsx`:
- Around line 352-368: 将 virtualProp 分支中的样式限定到当前 Conversations
实例:为该实例根元素添加虚拟模式修饰类,并通过现有样式系统生成包含该修饰类的作用域选择器。更新 style 中基于 prefixCls
的规则,使其仅匹配当前虚拟实例,避免影响其他非虚拟或不同实例。
- Around line 304-325: Update the flatData useMemo around groupList.forEach so
collapsed groups contribute their group title but skip their conversationInfo
entries. Include the relevant collapse-state source in the memo dependency array
so flatData recomputes when a group is expanded or collapsed.
- Around line 369-377: 调整 conversations 组件中的 VirtualList 配置,避免将其作为 ul 内的直接结构并产生
ul 包裹 div 的无效层级;移除或替换 component="ul",或重新组织外层容器,确保最终 ul 的直接子节点始终是
li,同时保持现有虚拟列表渲染行为。

---

Outside diff comments:
In `@packages/x/components/conversations/index.tsx`:
- Around line 340-350: 调整虚拟列表高度计算,使其在启用 Creation 时扣除 Creation 实际占用的垂直空间。围绕
virtualHeight、Creation 和 VirtualList 的布局逻辑监听实际列表区域或测量创建区域高度,确保两者总高度不超过根容器;未启用
creation 时保持现有高度行为。

---

Nitpick comments:
In `@packages/x/components/bubble/__tests__/list-virtual.test.tsx`:
- Around line 17-19: Update the mock list rendering around itemKey to support
both function and string forms accepted by BubbleList: invoke function itemKey
values, and resolve string itemKey values from each item for the React key, with
the existing index fallback when itemKey is absent. Do not call non-function
itemKey values as functions.

In `@packages/x/components/bubble/demo/virtual-list.tsx`:
- Around line 18-24: Unify React Hook usage by importing useMemo alongside
useState in virtual-list.tsx and replacing all React.useState and React.useMemo
calls with the destructured hooks. Apply the same changes to the example code in
packages/x/components/bubble/demo/virtual-list.md lines 32-38 so the source and
documentation remain identical.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0c7bdca5-7444-4b9c-92a5-b2b15c15410d

📥 Commits

Reviewing files that changed from the base of the PR and between 17db20b and 76f1a7a.

📒 Files selected for processing (17)
  • packages/x/components/bubble/BubbleList.tsx
  • packages/x/components/bubble/__tests__/list-benchmark.test.tsx
  • packages/x/components/bubble/__tests__/list-virtual.test.tsx
  • packages/x/components/bubble/demo/virtual-list.md
  • packages/x/components/bubble/demo/virtual-list.tsx
  • packages/x/components/bubble/index.en-US.md
  • packages/x/components/bubble/index.zh-CN.md
  • packages/x/components/bubble/interface.ts
  • packages/x/components/conversations/GroupTitle.tsx
  • packages/x/components/conversations/Item.tsx
  • packages/x/components/conversations/__tests__/virtual.test.tsx
  • packages/x/components/conversations/demo/virtual-list.md
  • packages/x/components/conversations/demo/virtual-list.tsx
  • packages/x/components/conversations/index.en-US.md
  • packages/x/components/conversations/index.tsx
  • packages/x/components/conversations/index.zh-CN.md
  • packages/x/package.json

Comment thread packages/x/components/bubble/__tests__/list-benchmark.test.tsx Outdated
Comment thread packages/x/components/bubble/BubbleList.tsx
Comment thread packages/x/components/bubble/BubbleList.tsx
Comment on lines +6 to +24
// Mock @rc-component/virtual-list
jest.mock('@rc-component/virtual-list', () => {
const React = require('react');
const MockVirtualList = React.forwardRef((props: any, ref: any) => {
React.useImperativeHandle(ref, () => ({
scrollTo: jest.fn(),
nativeElement: null,
}));
const { data, children, itemKey, virtual, itemHeight, height, ...rest } = props;
return (
<div data-testid="mock-virtual-list" {...rest}>
{data?.map((item: any, index: number) => (
<div key={itemKey ? itemKey(item) : index}>{children(item, index)}</div>
))}
</div>
);
});
return { __esModule: true, default: MockVirtualList };
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

补充使用真实 VirtualList 的大数据测试或基准。

此 mock 会同步渲染全部 1000 行,而当前断言仅验证组件未崩溃,无法发现虚拟窗口失效、动态高度错误或 DOM 节点数量退化。请至少增加一个不 mock 依赖的测试,验证 1000 条数据下仅渲染可视窗口,并记录基准指标。

Also applies to: 98-110

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/conversations/__tests__/virtual.test.tsx` around lines
6 - 24, 在 conversations 虚拟列表测试中补充至少一个不 mock `@rc-component/virtual-list` 的真实
VirtualList 测试,使用 1000 条数据验证仅渲染可视窗口而非全部行,并记录可用于检测 DOM 节点数量或渲染性能退化的基准指标;保留现有 mock
测试用于基础断言。

Comment thread packages/x/components/conversations/index.tsx Outdated
Comment on lines +352 to +368
{virtualProp ? (
<>
<style>{`
.${prefixCls} { overflow-y: hidden !important; padding: 0 !important; }
.${prefixCls} .rc-virtual-list-scrollbar { display: none !important; }
.${prefixCls} .rc-virtual-list-holder {
overflow-y: auto !important;
padding: 12px;
box-sizing: border-box;
}
.${prefixCls} .rc-virtual-list-holder-inner > .${prefixCls}-item {
margin-top: 4px;
}
.${prefixCls} .rc-virtual-list-holder-inner > .${prefixCls}-group + .${prefixCls}-item {
margin-top: 0;
}
`}</style>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

将虚拟模式样式限定到当前组件实例。

这些选择器直接修改所有同 prefixCls 的 Conversations。页面上只要存在一个虚拟实例,其他非虚拟实例也会被强制清除 padding、隐藏 overflow 和滚动条。请添加虚拟模式修饰类,并通过现有样式系统生成作用域规则。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/conversations/index.tsx` around lines 352 - 368, 将
virtualProp 分支中的样式限定到当前 Conversations
实例:为该实例根元素添加虚拟模式修饰类,并通过现有样式系统生成包含该修饰类的作用域选择器。更新 style 中基于 prefixCls
的规则,使其仅匹配当前虚拟实例,避免影响其他非虚拟或不同实例。

Comment thread packages/x/components/conversations/index.tsx
thus.wzy added 2 commits July 17, 2026 09:25
…rwarding

- flatData now respects collapsed state (enableCollapse + expandedKeys)
- Create VirtualGroupTitle helper component to properly forward ref
- Fixes CodeRabbit review feedback on PR ant-design#1979
…without mock

- Map block to VirtualList align option, use nullish check for key
- Add ResizeObserver to auto-scroll when content grows (streaming)
- Remove VirtualList mock from benchmark, use real implementation
- Fixes CodeRabbit review feedback on PR ant-design#1979

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/x/components/bubble/BubbleList.tsx (1)

324-328: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

VirtualList 不兼容 flex-direction: column-reverse,会导致溢出时滚动完全失效。

在 CSS Flexbox 机制中,如果对具备 overflow: auto 的滚动容器应用 flex-direction: column-reverse,当其内部内容的高度超过父容器时,超出的内容会向顶部溢出。这会导致浏览器的 scrollTop 锁定为 0,并且用户无法向上滚动去查看历史的溢出内容。

同时,@rc-component/virtual-list 所有的滚动事件监听、内容裁切以及节点位移计算(基于 scrollToppadding)都依赖标准的文档流正向排版。强行将它的容器翻转会导致虚拟列表的内部逻辑被彻底破坏。

修复建议:
不要在虚拟列表上通过反转 Flex 容器方向来实现消息较少时的贴底。若需保持贴底对齐,可以考虑在高度不足时动态注入占位的 padding-top,或者在启用 virtual 模式时放弃这个特定的短列表贴底表现以确保列表不发生滚动侧功能阻断。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/bubble/BubbleList.tsx` around lines 324 - 328, Remove
the conditional flexDirection: 'column-reverse' override from the style applied
to VirtualList. Preserve the standard column layout and, if short-list bottom
alignment is still required, implement it through padding or another
VirtualList-compatible mechanism without reversing the scroll container.
🧹 Nitpick comments (2)
packages/x/components/bubble/BubbleList.tsx (1)

149-149: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

请将 virtualListRef 补充上强类型。

当前依然使用了 any,这会导致针对虚拟列表 API(如 scrollTo 的参数及 nativeElement 等)的调用缺乏类型保护。建议从 @rc-component/virtual-list 导入 ListRef 类型并应用。

💻 建议的代码修改

首先,确保在文件顶部导入:

import type { ListRef } from '`@rc-component/virtual-list`';

然后修改引用声明:

-  const virtualListRef = React.useRef<any>(null);
+  const virtualListRef = React.useRef<ListRef>(null);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/bubble/BubbleList.tsx` at line 149, 为 BubbleList 组件中的
virtualListRef 移除 any 类型并补充 `@rc-component/virtual-list` 提供的 ListRef
类型;在文件顶部添加对应的类型导入,并保留 React ref 的 null 初始值。
packages/x/components/bubble/__tests__/list-benchmark.test.tsx (1)

68-74: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

建议修改基准测试中的断言方式,避免受环境波动影响发生误判。

由于测试运行在 jsdom 中,在此处断言渲染耗时 < 10000ms 预期过于宽松,既无法真实反映出虚拟列表的大数据量性能提升,也容易受 CI 环境整体负载的波动干扰。

既然移除了 Mock 并引入了真实组件,更严谨有效的验证方法是:直接校验虚拟列表是否对 DOM 节点进行了裁剪(例如,验证实际挂载在文档中的 .ant-bubble 节点数是否远小于给定的 1000 个),这比测量模拟环境下的渲染时间更有说服力。

💡 建议的代码修改
-  it('should measure render time for 1000 items in virtual mode', () => {
+  it('should mount reduced number of DOM nodes for 1000 items in virtual mode', () => {
     const items = generateLargeItems(1000);
 
-    const start = performance.now();
-    render(<BubbleList items={items} virtual style={{ height: 600 }} />);
-    const end = performance.now();
-
-    // Should render without crash in reasonable time
-    expect(end - start).toBeLessThan(10000);
+    const { container } = render(<BubbleList items={items} virtual style={{ height: 600 }} />);
+
+    // 验证虚拟列表对 DOM 节点进行了裁剪(实际挂载的节点应远少于 1000)
+    const bubbles = container.querySelectorAll('.ant-bubble');
+    expect(bubbles.length).toBeGreaterThan(0);
+    expect(bubbles.length).toBeLessThan(1000);
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/x/components/bubble/__tests__/list-benchmark.test.tsx` around lines
68 - 74, Replace the performance-duration assertion in the BubbleList benchmark
test with a DOM virtualization assertion: after rendering the 1000-item list,
query mounted .ant-bubble elements and verify their count is substantially below
the total item count. Keep the real BubbleList rendering and virtual
configuration unchanged, and remove the timing-based check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/x/components/bubble/BubbleList.tsx`:
- Around line 247-260: Update the align mapping in the scroll handling logic
around virtualListRef.current.scrollTo to use only the VirtualList-supported
values 'top', 'bottom', or 'auto'. Ensure the default block behavior remains
compatible with bottom scrolling: when top is 'bottom' and block is omitted,
align the final item at the bottom rather than defaulting to top; preserve
explicit block alignment behavior for supported values.
- Around line 226-237: Update the ResizeObserver setup in the virtual-list
effect near virtualListRef to observe the internal .rc-virtual-list-holder-inner
content element instead of listRef.current, while preserving the existing
requestAnimationFrame scrollTo behavior and cleanup.

---

Outside diff comments:
In `@packages/x/components/bubble/BubbleList.tsx`:
- Around line 324-328: Remove the conditional flexDirection: 'column-reverse'
override from the style applied to VirtualList. Preserve the standard column
layout and, if short-list bottom alignment is still required, implement it
through padding or another VirtualList-compatible mechanism without reversing
the scroll container.

---

Nitpick comments:
In `@packages/x/components/bubble/__tests__/list-benchmark.test.tsx`:
- Around line 68-74: Replace the performance-duration assertion in the
BubbleList benchmark test with a DOM virtualization assertion: after rendering
the 1000-item list, query mounted .ant-bubble elements and verify their count is
substantially below the total item count. Keep the real BubbleList rendering and
virtual configuration unchanged, and remove the timing-based check.

In `@packages/x/components/bubble/BubbleList.tsx`:
- Line 149: 为 BubbleList 组件中的 virtualListRef 移除 any 类型并补充
`@rc-component/virtual-list` 提供的 ListRef 类型;在文件顶部添加对应的类型导入,并保留 React ref 的 null
初始值。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 283a5591-702c-48ac-b2ee-bd6ab7b60407

📥 Commits

Reviewing files that changed from the base of the PR and between 06b0596 and 54bc3ad.

📒 Files selected for processing (2)
  • packages/x/components/bubble/BubbleList.tsx
  • packages/x/components/bubble/__tests__/list-benchmark.test.tsx

Comment thread packages/x/components/bubble/BubbleList.tsx
Comment thread packages/x/components/bubble/BubbleList.tsx Outdated
…Scroll

- align maps to VirtualList supported values: 'top' | 'bottom' | 'auto'
- bottom scroll defaults to align: 'bottom' when block is omitted
- ResizeObserver observes .rc-virtual-list-holder-inner instead of fixed container
- Fixes CodeRabbit review feedback on PR ant-design#1979
@wenzeyu8888-rgb

Copy link
Copy Markdown
Contributor Author

#1948

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OSS26-Team] Bubble.List / Conversations 大列表虚拟滚动

1 participant