-
-
Notifications
You must be signed in to change notification settings - Fork 280
Form.List name 不在 onFinish 取值中 #761
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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此PR为表单列表引入动态字段卸载/挂载支持。核心变更包括重构字段实体处理逻辑、添加灵活字段类型、新增示例和测试,以及实现字段值提取的精细控制机制。 Changes
Sequence DiagramsequenceDiagram
participant User
participant Form as Form Component
participant useForm
participant Validator as Field Validator
participant Store as Field Store
rect rgb(240, 248, 255)
Note over User,Store: 初始提交流程
User->>Form: 点击提交
Form->>useForm: validateFields()
useForm->>useForm: 构建finalValueNamePathList
useForm->>Store: getFieldsValue(finalValueNamePathList)
Store-->>useForm: 返回表单值(含所有字段)
useForm->>Validator: 触发验证
Validator-->>useForm: 验证通过
useForm->>Form: onFinish(values)
end
rect rgb(240, 255, 240)
Note over User,Store: 切换可见性阶段
User->>Form: 点击隐藏/显示按钮
Form->>Form: 重新渲染,age字段卸载
useForm->>Store: 标记age字段为INVALIDATE_NAME_PATH
end
rect rgb(255, 248, 240)
Note over User,Store: 再次提交流程(字段已卸载)
User->>Form: 点击提交
Form->>useForm: validateFields()
useForm->>useForm: finalValueNamePathList排除已卸载字段
useForm->>Store: getFieldsValue(finalValueNamePathList)
Store-->>useForm: 返回表单值(仅活跃字段)
useForm->>Form: onFinish(values)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 需要特别关注的区域:
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @crazyair, 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!
此拉取请求旨在解决 rc-field-form 中一个关键的表单值收集问题,即 Form.List 的名称在 onFinish 回调中未能正确获取。通过修改核心表单逻辑,确保即使列表中的字段被动态卸载,表单也能准确地捕获所有相关数据。此外,还引入了一个新的示例来验证并展示此修复。
Highlights
- 修复 Form.List 名称在 onFinish 中取值不正确的问题: 解决了当 Form.List 中的字段被条件渲染或卸载时,其名称无法正确包含在 onFinish 回调返回的值中的问题。
- 新增 Form.List 卸载示例: 添加了一个新的文档和示例,用于演示和测试 Form.List 在其内部字段被卸载时的行为,确保数据收集的正确性。
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| 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 issue 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
-
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. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
此拉取请求旨在修复当 Form.List 中的字段被卸载后,其旧值仍会包含在 onFinish 回调的表单值中的问题。核心改动是在 validateFields 方法中,当收集所有字段路径用于最终取值时,跳过 Form.List 自身的实体,仅依赖其已挂载的子字段实体来构建值。这能正确处理字段卸载的场景。然而,这个修复引入了一个严重的问题:当 Form.List 为空数组时,它在 onFinish 的结果中会丢失。我已经就此问题在 src/useForm.ts 中添加了具体的审查评论。
There was a problem hiding this 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 (6)
src/useForm.ts (2)
1003-1011: 确保返回值与 “不包含 List 名称” 目标一致并覆盖测试此处
returnPromise最终通过this.getFieldsValue(namePathList)生成values。由于上面已跳过 List 容器,namePathList仅包含叶子字段,最后合并出的对象会包含users的值树,但不会把 List 自身当作字段实体返回,符合预期。建议新增一个用例覆盖:
- 表单含
Form.List name="users",仅选中子字段时,onFinish(values)中没有单独的 “List 容器字段” 项,但values.users的数组结构完整。我可以基于现有测试框架起草一个最小复现用例。
309-351: getFieldsValue 的 List 过滤语义与 validateFields 对齐性这里在
strict与非strict下分别跳过isList与isListField。从行为上看与本次改动一致,但建议在源码注释中明确两者差异(List 容器 vs. List 子字段),避免后续维护者误用;同时补一条单测覆盖getFieldsValue({ strict: true })与默认模式的差异,特别是在条件卸载子字段时的表现。docs/demo/list-unmount.md (1)
1-3: 标题与其他示例保持命名风格一致建议将小写的
## list调整为## List(或中文标题,如“列表:条件卸载字段”),与文档其它章节风格统一,便于检索。docs/examples/list-unmount.tsx (3)
25-25: 演示区块可读性小优化
<Form.Field shouldUpdate>直接渲染 JSON 字符串可读性一般,建议包裹在<pre>以保留缩进格式,或使用一个只读文本区域,便于对比strict与非strict的差异。- <Form.Field shouldUpdate>{() => JSON.stringify(form.getFieldsValue(), null, 2)}</Form.Field> + <Form.Field shouldUpdate> + {() => <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>} + </Form.Field>
33-41: 利用 strict 对比“已注册字段 vs. 保留值”的效果非常好,建议在示例内补一行注释说明意图当前示例通过条件卸载
age字段并对比getFieldsValue()与getFieldsValue({ strict: true }),很好地演示了差异。为减少读者困惑,建议在此处添加一句注释说明:当age被卸载时,默认模式仍会返回保留的值,而strict只返回当前已挂载字段的值。
47-49: 按钮文案更明确一些将“隐藏”调整为“隐藏/显示年龄”或增加
aria-pressed,对可读性与无障碍更友好。- <button type="button" onClick={() => setIsShow(c => !c)}> - 隐藏 - </button> + <button + type="button" + aria-pressed={!isShow} + onClick={() => setIsShow(c => !c)} + > + 隐藏/显示年龄 + </button>
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
docs/demo/list-unmount.md(1 hunks)docs/examples/list-unmount.tsx(1 hunks)src/useForm.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
docs/examples/list-unmount.tsx (1)
tests/common/InfoField.tsx (1)
Input(9-11)
🔇 Additional comments (2)
src/useForm.ts (1)
924-926: 验证 Form.List 容器定向校验兼容性
本地运行完整测试并确认以下场景符合预期:
- 调用 validateFields(['users']) 且 recursive: false 时是否跳过子项
- recursive: true 或传入父/子路径时的校验行为
在文档中明确说明不支持对 List 容器直接配置校验规则。docs/examples/list-unmount.tsx (1)
1-5: 示例组件依赖已存在
已在docs/examples/components下找到Input.tsx与LabelField.tsx,文档站构建不会因缺失此处组件而失败。
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #761 +/- ##
=======================================
Coverage 99.53% 99.53%
=======================================
Files 19 19
Lines 1289 1297 +8
Branches 320 322 +2
=======================================
+ Hits 1283 1291 +8
Misses 6 6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@gemini-code-assist Review it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
这个 PR 修复了在 Form.List 中,已卸载的字段仍然会被包含在 onFinish 的 values 中的问题。通过在 useForm.ts 中改用 getFieldsValue({ strict: true }),确保了在表单校验成功后,只收集已挂载字段的值,正确地解决了这个问题。新增的 list-unmount.tsx 示例和相关文档非常棒,清晰地展示了修复效果。
不过,这个修改也可能改变了 validateFields 的行为,它现在总是返回所有字段的值,而不是像以前一样只返回被校验字段的值。我在代码中留了一条评论,对此进行了更详细的说明,并指出了成功和失败路径下取值逻辑可能存在的不一致性。请查看。
|
@gemini-code-assist, pls help to CR again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 修复了 Form.List 在动态表单场景下,因列表项卸载导致 onFinish 回调中表单值不正确的问题。同时添加了对应的文档和示例。
代码改动主要在 src/useForm.ts 的 validateFields 方法中,通过避免重复添加列表字段路径来解决问题。
整体来看,PR 的目标明确,修复是有效的。我有一个关于路径匹配逻辑的建议,希望能让代码更健壮。
|
@gemini-code-assist Review it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 修复了在 Form.List 中动态卸载字段时 onFinish 可能获取到不正确值的问题。核心改动在 useForm.ts 中,通过调整 validateFields 方法里的字段收集逻辑,避免了列表字段和其子字段路径被重复添加,从而保证了取值的正确性。同时,PR 也新增了对应的单元测试和文档示例,覆盖了修复的场景。整体来看,代码改动清晰,测试和文档补充也很到位。我在 useForm.ts 中留下了一条关于代码健壮性的建议,旨在提高代码的长期可维护性,请查阅。
src/useForm.ts
Outdated
| if (!provideNameList) { | ||
| namePathList.push(field.getNamePath()); | ||
| if (field.isList() && namePathList.some(name => matchNamePath(name, fieldNamePath, true))) { | ||
| removeListNameStrList.push(fieldNamePath.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个逻辑很奇怪,这里 matchNamePath 一次后,下面又从列表里匹配一遍。
应该是提取一个 filledPathList 专门存需要留档的路径出来,避免多轮遍历。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
要用2个 list吗?也行吧,就是 push 的地方要写好几处地方,比较麻烦。
我现在实现的,就是单纯记录 List 这层的 name,然后在提交时候过滤。
|
@gemini-code-assist pls help to review again and provide more detail action. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 修复了 Form.List 中动态卸载的字段值仍会出现在 onFinish 回调中的问题。实现方式是通过在 validateFields 方法中区分“需要校验的字段列表”和“最终提交时需要取值的字段列表”,确保在提交时只包含当前已挂载的字段值。
改动逻辑清晰,并且通过新增的示例代码和单元测试充分覆盖了修复的场景,确保了代码质量。我对其中一处实现的复杂度和潜在性能问题提出了改进建议,但总体而言,这是一次高质量的提交。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/useForm.ts(3 hunks)tests/list.test.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/useForm.ts
🧰 Additional context used
🧬 Code graph analysis (1)
tests/list.test.tsx (3)
tests/common/InfoField.tsx (1)
Input(9-11)src/Field.tsx (1)
render(660-681)tests/common/timeout.ts (1)
timeout(3-7)
|
@zombieJ is attempting to deploy a commit to the React Component Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/useForm.ts (2)
323-354: 填充空 List 的判定应更精确当前用
!getValue(...)判空会把null/0/''等“假值”误判。建议仅在undefined时填充[],避免语义偏差。- listNamePaths.forEach(namePath => { - if (!getValue(mergedValues, namePath)) { - mergedValues = setValue(mergedValues, namePath, []); - } - }); + listNamePaths.forEach(namePath => { + if (getValue(mergedValues, namePath) === undefined) { + mergedValues = setValue(mergedValues, namePath, []); + } + });
1018-1033: 对齐validateFields成功/失败返回结构成功路径返回
getFieldsValue(finalValueNamePathList),而失败路径返回getFieldsValue(namePathList),两者结构不一致,容易踩坑。建议失败路径也改为finalValueNamePathList。- return Promise.reject({ - message: errorMessage, - values: this.getFieldsValue(namePathList), + return Promise.reject({ + message: errorMessage, + values: this.getFieldsValue(finalValueNamePathList), errorFields: errorList, outOfDate: this.lastValidatePromise !== summaryPromise, });
♻️ Duplicate comments (1)
src/useForm.ts (1)
919-945: 关于finalValueNamePathList的构建建议补注释这里对
Form.List父路径做了排除,仅在不存在子字段时才纳入finalValueNamePathList。建议加一行注释阐明:该列表仅用于“最终值收集”,避免同时包含父 List 与其子字段导致重复/覆盖问题,便于维护者理解上下文。
🧹 Nitpick comments (4)
src/interface.ts (1)
120-124: 命名与注释可读性建议属性名使用全大写在接口字段中不常见,容易与常量混淆;且 “Mask as invalidate” 语义略生硬。建议改为更贴近语义的驼峰命名与注释,降低心智负担(保持向后兼容可先双写再迁移)。
- /** - * Mask as invalidate. - * This will filled when Field is removed but not updated in render yet. - */ - INVALIDATE_NAME_PATH?: InternalNamePath; + /** + * Mark this entity as a placeholder for an invalidated field. + * Filled when the Field has been removed but render has not updated yet. + */ + invalidatedNamePath?: InternalNamePath;若已在其余文件使用该字段,可先保留旧名并标注废弃,后续统一替换。
src/utils/valueUtil.ts (1)
23-31: 为 newStore 明确类型并微调循环避免
{}触发隐式 any,顺带用 for‑of 减少中间数组与闭包开销。-export function cloneByNamePathList(store: Store, namePathList: InternalNamePath[]): Store { - let newStore = {}; - namePathList.forEach(namePath => { - const value = getValue(store, namePath); - newStore = setValue(newStore, namePath, value); - }); - - return newStore; -} +export function cloneByNamePathList(store: Store, namePathList: InternalNamePath[]): Store { + let newStore: Store = {}; + for (const namePath of namePathList) { + const value = getValue(store, namePath); + newStore = setValue(newStore, namePath, value); + } + return newStore; +}tests/list.test.tsx (1)
1015-1044: 补充空列表回归用例,防止再次回归为确保当
Form.List为空时仍返回[](而非缺失或undefined),建议增加专门测试用例。+ it('empty Form.List should be kept as [] in getFieldsValue and onFinish', async () => { + const onFinish = jest.fn(); + const formRef = React.createRef<FormInstance>(); + const { queryByTestId } = render( + <Form ref={formRef} initialValues={{ users: [] }} onFinish={onFinish}> + <Form.List name="users">{() => null}</Form.List> + <button type="submit" data-testid="submit">Submit</button> + </Form>, + ); + expect(formRef.current?.getFieldsValue()).toEqual({ users: [] }); + fireEvent.click(queryByTestId('submit')!); + await act(async () => { await timeout(); }); + expect(onFinish).toHaveBeenCalledWith({ users: [] }); + });src/useForm.ts (1)
787-791:onValuesChange的合并可能冗余
updateStore已在前面完成,this.getFieldsValue()理论上可读到最新值;再次merge多一步拷贝。若无特别场景(例如getFieldsValue过滤导致缺项),可直接传this.getFieldsValue(),以减少一次对象合并及内存分配。若确需保底,请在注释中说明原因。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/Form.tsx(1 hunks)src/List.tsx(1 hunks)src/interface.ts(1 hunks)src/useForm.ts(9 hunks)src/utils/valueUtil.ts(1 hunks)tests/list.test.tsx(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/Form.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
tests/list.test.tsx (3)
src/interface.ts (1)
FormInstance(262-284)tests/common/InfoField.tsx (1)
Input(9-11)tests/common/timeout.ts (1)
timeout(3-7)
src/useForm.ts (2)
src/interface.ts (3)
FieldEntity(96-125)NamePath(7-7)InternalNamePath(6-6)src/utils/valueUtil.ts (5)
cloneByNamePathList(23-31)getNamePath(15-17)getValue(6-6)setValue(6-6)matchNamePath(53-67)
🔇 Additional comments (3)
src/List.tsx (1)
4-4: 变更确认仅导入来源调整,无行为变化,整体 LGTM。
tests/list.test.tsx (2)
599-602: 隔离断言噪音的好做法在触发 remove 之前对
onValuesChange执行mockReset()可以有效隔离先前调用,便于只断言当次变更,赞。同类用例可复用此手法。
942-1013: 覆盖“卸载字段不应出现在提交值”场景,赞“list unmount” 用例验证了条件卸载后
onFinish与getFieldsValue()的取值一致性,匹配 PR 目标,LGTM。
|
@gemini-code-assist 帮忙 CR 一下代码,看看有没有什么 edge case 没有处理 |
1 similar comment
|
@gemini-code-assist 帮忙 CR 一下代码,看看有没有什么 edge case 没有处理 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
这个 PR 修复了 Form.List 中已卸载字段仍然存在于 onFinish 的返回值的 bug,做得很好。相关的逻辑修改看起来是合理的,并且通过新增的示例和单元测试,覆盖了核心场景,保证了修复的有效性。
我发现了一个潜在的严重问题和一处可以改进的地方:
- 在
src/useForm.ts中,onValuesChange回调中使用了深合并(merge)来生成allValues,这在处理数组删除操作时可能会导致不正确的结果。 - 在新的示例文件
docs/examples/list-unmount.tsx中,使用了已被废弃的strict: true选项,这可能会让使用者感到困惑。
除此之外,其他部分的修改看起来都很不错。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 主要优化了 Form.List 在动态挂载/卸载字段时的值的处理逻辑,确保 onFinish 能获取到正确的表单值,并完善了对复杂列表结构的支持。同时,添加了相应的示例和测试用例,并废弃了 getFieldsValue 中的 strict 选项。
整体来看,这些改动提升了表单库的稳定性和易用性。我发现了一个核心逻辑中可能存在的依赖字段处理顺序的 bug,这在某些情况下可能导致 onFinish 的值不正确。此外,新添加的示例中使用了已被废弃的 API 选项。具体细节请查看我的评论。
Summary by CodeRabbit
发布说明
新功能
改进
测试