目录
- 一、背景
- 二、原因分析
- 问题背景
- 场景举例
- 场景 1:自定义表单控件
- 场景 2:多个表单
- 三、解决方案
- 解决方案 1:自定义控件需要转发 `ref`
- 解决方案 2:区分多个表单的 `name`
- 验证代码
- 总结
一、背景
在 WHAT - Form 表单“提交失败自动滚动到第一个错误字段”的原理 中我们已介绍过相关原理。
但在实际使用中,我们经常会出现自定义 form item 组件无法实现自动滚动到错误字段的问题。
二、原因分析
在 Antd 官方 FAQ(Frequently Asked Questions) 中有如下解释:
//1. 使用了自定义表单控件//从 5.17.0 版本开始,滑动操作将优先使用表单控件元素所转发的 ref 元素。因此,在考虑自定义组件支持校验滚动时,请优先考虑将其转发给表单控件元素。//滚动依赖于表单控件元素上绑定的 id 字段,如果自定义控件没有将 id 赋到正确的元素上,这个功能将失效。你可以参考这个 codesandbox。//2. 页面内有多个表单//页面内如果有多个表单,且存在表单项 name 重复,表单滚动定位可能会查找到另一个表单的同名表单项上。需要给表单 Form 组件设置不同的 name 以区分。
上述问题与 Ant Design 的表单滚动定位功能有关,以下是具体说明:
问题背景
-
自定义表单控件导致滚动失效:
- 表单的
scrollToFirstError
和scrollToField
功能依赖表单项中的控件有正确绑定的id
。 - 如果使用了自定义表单控件,并且未将
id
正确绑定到实际的输入元素,Ant Design 无法准确地定位滚动目标。
- 表单的
-
页面中有多个表单:
- 如果页面中有多个表单,且它们的表单项
name
存在重复,滚动功能可能会滚动到错误的表单项,因为它无法区分是哪一个表单中的项。
- 如果页面中有多个表单,且它们的表单项
场景举例
场景 1:自定义表单控件
// 问题代码:自定义组件未转发 ref
const CustomInput: React.FC = (props) => {return <input {...props} />; // 没有转发 ref
};<Form><Form.Itemname="username"rules={[{ required: true, message: "请输入用户名!" }]}><CustomInput /></Form.Item>
</Form>;// 当触发 `scrollToFirstError` 时,无法正确滚动到该控件
场景 2:多个表单
// 问题代码:多个表单的项使用相同的 name
<Form name="form1"><Form.Item name="username" rules={[{ required: true }]}><Input /></Form.Item>
</Form>
<Form name="form2"><Form.Item name="username" rules={[{ required: true }]}><Input /></Form.Item>
</Form>;// 当触发 `scrollToField('username')` 时,可能滚动到错误的表单
三、解决方案
解决方案 1:自定义控件需要转发 ref
确保自定义控件通过 React.forwardRef
正确转发 ref
,并将 id
绑定到最终的实际输入元素。
// 正确的自定义控件
import React from "react";const CustomInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>((props, ref) => {return <input ref={ref} {...props} />;
});// 在表单中使用
<Form><Form.Itemname="username"rules={[{ required: true, message: "请输入用户名!" }]}><CustomInput /></Form.Item>
</Form>;
解决方案 2:区分多个表单的 name
在页面中有多个表单时,为每个表单组件设置唯一的 name
属性:
<Form name="form1"><Form.Item name="username" rules={[{ required: true }]}><Input /></Form.Item>
</Form>
<Form name="form2"><Form.Item name="username" rules={[{ required: true }]}><Input /></Form.Item>
</Form>;
这样可以避免滚动功能因重复 name
找到错误的表单项。
验证代码
以下是一个完整的示例:
import React from "react";
import { Form, Input, Button } from "antd";const CustomInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>((props, ref) => {return <input ref={ref} {...props} />;
});const App: React.FC = () => {const [form] = Form.useForm();const handleSubmit = () => {form.validateFields().catch(() => {form.scrollToField("username", {behavior: "smooth",});});};return (<Form form={form} name="form1" style={{ marginTop: 20 }}><Form.Itemlabel="用户名"name="username"rules={[{ required: true, message: "请输入用户名!" }]}><CustomInput /></Form.Item><Button type="primary" onClick={handleSubmit}>提交</Button></Form>);
};export default App;
总结
- 自定义控件问题:确保自定义控件正确转发
ref
并绑定id
。 - 多个表单问题:为每个表单设置唯一的
name
,避免重复表单项导致定位错误。 - 工具支持:可以结合
scrollToFirstError
和scrollToField
的options
参数(如block
和behavior
)调整滚动行为。