Skip to content

Conversation

@QDyanbing
Copy link
Contributor

@QDyanbing QDyanbing commented Jan 4, 2026

修复说明

虚拟表格在数据缩小时,rowSpan 高度计算可能使用过期索引,
导致访问越界的 flattenData 并触发运行时错误。

本次在 getHeight 中增加防御性处理,避免过渡状态下崩溃,
不影响正常渲染行为。

fix ant-design/ant-design#56459

Summary by CodeRabbit

  • 错误修复
    • 改进了虚拟表格在含多行合并单元格时的高度计算容错:当计算到的结束项缺失或无效时会退回到安全高度计算路径,避免渲染异常或崩溃,提升在滚动、缩放或分页变化下的稳定性。
  • 测试
    • 新增用例验证在滚动到底部后缩小页大小(含 rowSpan 情况)不会抛出异常或记录未定义访问错误。

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 4, 2026

@QDyanbing is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

为虚拟表格在处理跨行合并(rowSpan)时的高度计算加入防护:当跨行结束项缺失或其 record 为 undefined 时,改为使用最后可用项或退回单行高度,避免读取不存在的 record 导致异常;并新增回归测试覆盖 pageSize 缩小场景。

Changes

Cohort / File(s) 改动摘要
虚拟表格高度计算防护
src/VirtualTable/BodyGrid.tsx
在 extraRender 的高度计算中添加防御性分支:若计算出的 span 结束项缺失或其 recordundefined,则使用最后可用结束项(safeEndIndex)或回退到单行高度,避免访问不存在的 record 导致异常。
测试:缩小 pageSize 场景
tests/Virtual.spec.tsx
新增测试 should not crash when pageSize shrinks after scrolled to bottom (rowSpan):模拟滚动到底部后将 pageSize 缩小并重新渲染,断言不抛异常且不在控制台记录因 undefined record 导致的错误。

Sequence Diagram(s)

(本次改动为防御性修复,未引入跨多个组件的新控制流,故省略序列图。)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 分钟

Suggested reviewers

  • zombieJ

Poem

🐰 滑到底部心一惊,
缺项护住不慌张,
回退最后稳如常,
测试守护不留伤,
小改一行笑开颜。

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确总结了主要变更:在虚拟表格中守护rowSpan高度计算,防止数据缩小时的问题。
Linked Issues check ✅ Passed 代码变更完全满足关联issue的要求,通过在getHeight中增加防御性处理来防止访问越界的flattenData。
Out of Scope Changes check ✅ Passed 所有变更都与修复虚拟表格rowSpan高度计算和数据缩小问题直接相关,无超出范围的改动。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec5d680 and 485def7.

📒 Files selected for processing (1)
  • src/VirtualTable/BodyGrid.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/VirtualTable/BodyGrid.tsx

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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @QDyanbing, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在解决虚拟表格在数据量减少时,rowSpan 高度计算逻辑中存在的潜在崩溃问题。通过在计算行高时引入对 flattenData 索引的边界检查和回退机制,确保即使在数据结构发生变化时,系统也能安全地获取行信息,从而避免了因访问过期或越界索引而导致的运行时错误,提升了组件的健壮性。

Highlights

  • 修复虚拟表格崩溃: 解决了虚拟表格在数据缩小时,rowSpan 高度计算可能导致访问越界并触发运行时错误的问题。
  • 增强防御性处理: 在 getHeight 函数中增加了防御性检查,确保即使在数据过渡状态下也能避免崩溃。
  • 不影响正常渲染: 此次修复旨在提高稳定性,不会对表格的正常渲染行为产生负面影响。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

本次 PR 修复了虚拟表格在数据缩小时,rowSpan 高度计算可能使用过期索引导致越界访问的问题。通过在 getHeight 方法中增加防御性处理,有效避免了运行时错误。代码逻辑正确,解决了所述问题。我提出了一点关于代码结构优化的建议,以提高代码的可读性和可维护性。

Comment on lines 195 to 216
const endItem = flattenData[endItemIndex];

if (!endItem || !endItem.record) {
// clamp 到当前可用的最后一行,或退化为默认高度
const safeEndIndex = Math.min(endItemIndex, flattenData.length - 1);
const safeEndItem = flattenData[safeEndIndex];

if (!safeEndItem || !safeEndItem.record) {
// 兜底:没有任何安全 endItem,就返回单行高度
const single = getSize(rowKey);
return single.bottom - single.top;
}

const endItemKey = getRowKey(safeEndItem.record, safeEndIndex);
const sizeInfo = getSize(rowKey, endItemKey);
return sizeInfo.bottom - sizeInfo.top;
}

const endItemKey = getRowKey(endItem.record, endItemIndex);

const sizeInfo = getSize(rowKey, endItemKey);
return sizeInfo.bottom - sizeInfo.top;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

这里的逻辑是正确的,但存在一些代码重复,并且可以组织得更清晰。我们可以通过重构来简化代码,将正常路径和回退路径的逻辑合并,以避免重复并提高可读性。

        let targetItem = flattenData[endItemIndex];
        let targetIndex = endItemIndex;

        if (!targetItem || !targetItem.record) {
          // clamp 到当前可用的最后一行
          targetIndex = Math.min(endItemIndex, flattenData.length - 1);
          targetItem = flattenData[targetIndex];

          // 兜底:没有任何安全 endItem,就返回单行高度
          if (!targetItem || !targetItem.record) {
            const single = getSize(rowKey);
            return single.bottom - single.top;
          }
        }

        const endItemKey = getRowKey(targetItem.record, targetIndex);
        const sizeInfo = getSize(rowKey, endItemKey);
        return sizeInfo.bottom - sizeInfo.top;

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/VirtualTable/BodyGrid.tsx (1)

198-203: 代码注释应使用英文以保持项目一致性。

代码中的中文注释(第 198 行和第 203 行)应改为英文,以匹配项目的编码规范和国际化需求。

🔎 建议的英文注释
-          // clamp 到当前可用的最后一行,或退化为默认高度
+          // Clamp to the last available row, or fall back to default height
           const safeEndIndex = Math.min(endItemIndex, flattenData.length - 1);
           const safeEndItem = flattenData[safeEndIndex];
 
           if (!safeEndItem || !safeEndItem.record) {
-            // 兜底:没有任何安全 endItem,就返回单行高度
+            // Fallback: if no safe endItem exists, return single row height
             const single = getSize(rowKey);
             return single.bottom - single.top;
           }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5254ebc and 7bc4891.

📒 Files selected for processing (1)
  • src/VirtualTable/BodyGrid.tsx
🔇 Additional comments (2)
src/VirtualTable/BodyGrid.tsx (2)

195-217: 修复有效:防御性处理避免了运行时错误。

这段代码正确地解决了 PR 目标中描述的问题——当数据缩减时(例如分页从 200 条变为 10 条),endItemIndex 可能超出 flattenData 的范围,导致 endItem.record 访问未定义对象而抛出 TypeError。

新增的防御逻辑:

  1. 检查 endItem 是否存在
  2. 将索引钳制到数组边界内
  3. 提供单行高度作为最终降级方案

这确保了虚拟表格在数据转换期间不会崩溃。

基于学习记录,请在实际使用场景中验证此修复是否完全解决了 ant-design/ant-design#56459 中报告的问题,特别是在快速切换分页大小时的表现。


197-211: 当前的防御性检查已能正确处理边界情况。

代码在 end < 0 时已有守卫(第135行),防止在无数据时执行。同时,getHeight 仅在 spanLines 非空时才会被调用,而 spanLines 的构造依赖于 flattenData 中存在 rowSpan 的项目。这意味着当 flattenData 为空时,spanLines 也为空,getHeight 不会被执行,因此不会到达第199行的 Math.min(endItemIndex, -1) 路径。

即便代码路径被到达,第197-202行的防御性检查(!safeEndItem || !safeEndItem.record)也能正确处理 -1 索引导致的 undefined 情况。

关于降级到单行高度的视觉行为,当前处理是合理的防御性设计。建议通过添加注释说明这种降级行为是预期的,以增加代码可维护性。

@afc163
Copy link
Member

afc163 commented Jan 4, 2026

是否好写用例?

@codecov
Copy link

codecov bot commented Jan 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.10%. Comparing base (5254ebc) to head (485def7).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1433   +/-   ##
=======================================
  Coverage   96.09%   96.10%           
=======================================
  Files          57       57           
  Lines        3434     3442    +8     
  Branches      632      637    +5     
=======================================
+ Hits         3300     3308    +8     
  Misses        129      129           
  Partials        5        5           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@QDyanbing
Copy link
Contributor Author

补充了一个

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/Virtual.spec.tsx (1)

640-720: 测试用例设计合理,有效覆盖了数据缩减场景下的崩溃问题。

该测试准确复现了 issue #56459 中描述的问题场景:

  1. 创建大数据集并使用 rowSpan
  2. 滚动到底部
  3. 缩减数据量(从 200 条到 10 条)
  4. 验证不会抛出异常且无 undefined 访问错误

测试结构清晰,断言充分。

可选的增强建议

可以考虑在数据缩减后添加正向断言,验证表格仍正确渲染:

     // 不应出现 record undefined 相关错误
     const errorText = errSpy.mock.calls.map(args => String(args[0] ?? '')).join('\n');
     expect(errorText).not.toContain('Cannot read properties of undefined');
     expect(errorText).not.toContain("reading 'record'");
+
+    // 验证表格正确渲染了新数据
+    const rows = container.querySelectorAll('.rc-table-row');
+    expect(rows.length).toBeGreaterThan(0);

这能确保表格在数据缩减后不仅没有崩溃,而且能正常显示内容。不过对于回归测试来说,当前的断言已经足够。

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7bc4891 and 944127f.

📒 Files selected for processing (1)
  • tests/Virtual.spec.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-08T12:53:09.293Z
Learnt from: bbb169
Repo: react-component/table PR: 1202
File: src/Table.tsx:903-904
Timestamp: 2024-11-08T12:53:09.293Z
Learning: 在 `src/Table.tsx` 文件的 React 组件 `Table` 中,即使 `bodyScrollLeft` 频繁更新,也需要在 `TableContextValue` 的 `useMemo` 依赖数组中包含 `bodyScrollLeft` 和 `headerCellRefs`,因为每次滚动时重新计算 `TableContextValue` 是解决该问题所必须的。

Applied to files:

  • tests/Virtual.spec.tsx
🧬 Code graph analysis (1)
tests/Virtual.spec.tsx (2)
src/interface.ts (1)
  • Reference (48-51)
src/index.ts (2)
  • Reference (24-24)
  • VirtualTable (21-21)

@afc163 afc163 merged commit 2b96b0a into react-component:master Jan 4, 2026
7 of 8 checks passed
@QDyanbing QDyanbing deleted the fix/getHeight branch January 5, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Table] 表格组件开启虚拟滚动分页报错

2 participants