Arco Design Form 表单外部控制表单项错误状态:setFields 方法实战指南
Arco Design Form 表单外部控制表单项错误状态:setFields 方法实战指南
本文聚焦 Arco Design React 组件库 Form 表单的
setFields方法,讲解如何通过该方法在表单外部(而非依赖用户交互)控制表单项的错误状态、警告信息与字段值。你将掌握setFields的完整参数结构、与setFieldValue/setFieldsValue/resetFields等方法的配合用法,以及错误状态在底层Store与Control中的流转机制,从而在联动校验、服务端返回错误、异步数据回填等场景下写出可靠的受控表单。
核心示例:通过 setFields 控制表单项错误状态
Arco Design Form 组件(仓库路径:components/Form)提供 Form.useForm() 获取表单实例,其中 setFields 方法允许你在任意时刻(事件回调、useEffect、接口回调等外部逻辑中)直接设置指定表单项的值、错误信息和警告信息。
官方演示 components/Form/demo/setFields.md 给出了完整可运行的示例:
import { Form, Input, Button, InputNumber } from '@arco-design/web-react';
const FormItem = Form.Item;
function App() {
const [form] = Form.useForm();
return (
<Form
form={form}
style={{ width: 600 }}
initialValues={{ name: 'admin' }}
autoComplete="off"
onValuesChange={(v, vs) => {
console.log(v, vs);
}}
onSubmit={(v) => {
console.log(v);
}}
>
<FormItem label="Username" field="name" rules={[{ required: true }]}>
<Input placeholder="please enter your username" />
</FormItem>
<FormItem
label="Age"
field="age"
rules={[{ required: true, type: 'number', min: 0, max: 99 }]}
>
<InputNumber placeholder="please enter your age" />
</FormItem>
<FormItem wrapperCol={{ offset: 5 }}>
<Button type="primary" htmlType="submit" style={{ marginRight: 24 }}>
Submit
</Button>
<Button
style={{ marginRight: 24 }}
onClick={() => {
form.resetFields();
}}
>
Reset
</Button>
<Button
type="text"
onClick={() => {
form.setFields({
age: {
value: 200,
error: {
message: 'Maximum is 200',
},
warning: <div>warning info ...</div>,
},
});
}}
>
Set Error Age
</Button>
</FormItem>
</Form>
);
}
export default App;
点击 "Set Error Age" 按钮后,age 字段被设置为 200(超出 rules 中 max: 99 的限制),同时外部强制写入错误信息 "Maximum is 200" 与警告节点。此时 Form.Item 会呈现 error 校验状态,错误文案显示在表单项下方。
setFields 方法签名与参数解析
根据 components/Form/README.zh-CN.md 的方法表,setFields 的类型为:
Function({ [field]: string: { value: any, error: FieldError } })
在 components/Form/store.tsx 中,setFields 的真实实现签名如下:
public setFields = (obj: {
[field in FieldKey]?: {
value?: FieldValue;
error?: FieldError<FieldValue>;
touched?: boolean;
warning?: React.ReactNode;
};
}) => { ... }
FieldError 的定义位于 components/Form/interface.tsx:
export type FieldError<FieldValue = any> = {
value?: FieldValue;
message?: ReactNode;
type?: string;
requiredError?: boolean;
};
各参数含义如下:
| 参数 | 类型 | 说明 |
|---|---|---|
value |
FieldValue |
要写入该字段的新值,会更新表单内部 store 并触发 onValuesChange |
error |
FieldError |
错误信息对象,核心字段为 message(ReactNode,可传字符串或任意 React 节点);也支持 type、requiredError 等扩展字段 |
warning |
React.ReactNode |
警告信息,可传字符串或 JSX 节点;warning 状态不会阻塞表单提交 |
touched |
boolean |
是否标记字段被"操作过",影响 getTouchedFields 的返回值 |
结合 store.tsx 中 setFields 的实现 可以看到,setFields 会遍历传入对象的每个字段,将 error / warning / touched / value 分别收集进 StoreChangeInfo['data'],写入 store 后通过 notify('setFieldValue', ...) 通知对应 Control,最后触发 triggerValuesChange(changeValues) 回调 onValuesChange。
错误状态在底层的流转机制
为了正确使用 setFields,理解错误状态如何"到达" Form.Item 很有价值。
Control:每个字段的控制器
每个 Form.Item 对应一个 Control 实例(components/Form/control.tsx)。当 setFields 触发 setFieldValue 通知时,Control.onStoreChange 会读取 info.data:
case 'setFieldValue':
if (isFieldMatch(field, fields)) {
this.toggleTouched(true);
if (info.data && 'touched' in info.data) {
this.toggleTouched(info.data.touched);
}
if (info.data && 'warnings' in info.data) {
this.setWarnings(
isNullOrUndefined(info.data.warnings) ? [] : [].concat(info.data.warnings)
);
}
if (info.data && 'errors' in info.data) {
this.setErrors(info.data.errors);
} else if (!isEqualWith(get(info.prev, field), get(info.current, field))) {
this.setErrors(null);
}
this.updateFormItem();
return;
}
...
值得注意的两个细节:
- 传入
error时:直接调用setErrors(info.data.errors),完全以外部传入的错误信息为准(不会因为值变化而自动清除)。 - 未传入
error但值发生变化时:会通过isEqualWith对比新旧值,若值确实改变则setErrors(null)清空错误——这正是"修改值自动清除旧错误"行为的来源。 warning为null或undefined时会被归一为[]清空警告;传字符串或节点时会被[].concat()包装为数组。
Form.Item:渲染错误与警告
components/Form/form-item.tsx 汇总各字段的 errors / warnings:
const errorInfo = errors ? Object.values(errors) : [];
const warningInfo = warnings
? Object.values(warnings).reduce((total, next) => total.concat(next), [])
: [];
const itemStatus = useMemo(() => {
if (errorInfo.length) {
return VALIDATE_STATUS.error;
}
if (warningInfo.length) {
return VALIDATE_STATUS.warning;
}
...
}, [errors, warnings, validateStatus]);
当 errorInfo 非空时,Form.Item 的校验状态变为 error,并通过 FormItemTip 组件渲染错误与警告文案(message-help-error 与 message-help-warning 两个类名分别对应错误/警告提示块)。
setFields 与其他表单方法的配合
与 setFieldValue / setFieldsValue 的关系
从 store.tsx 的实现可以看到:
// 外部调用设置表单字段值
public setFieldValue = (field: FieldKey, value: FieldValue) => {
if (!field) return;
this.setFields({
[field]: { value },
} as any);
};
// 外部调用,设置多个表单控件的值
public setFieldsValue = (values: DeepPartial<FormData>) => {
if (isObject(values)) {
const fields = Object.keys(values);
const obj = {};
fields.forEach((field) => {
obj[field] = { value: values[field] };
});
this.setFields(obj);
}
};
可见 setFieldValue 与 setFieldsValue 都是 setFields 的简化封装:它们仅设置 value,不携带错误信息。setFields 则是三者中能力最完整的"底层"方法——既设置值,也能注入错误与警告。
与 resetFields 的配合
resetFields 在 store.tsx 中将字段重置为 initialValues(或 initialValue),并触发 notify('reset', ...) 清空 touchedFields。演示示例中 "Reset" 按钮即调用 form.resetFields() 将 name 恢复为 'admin'、age 恢复为初始值,同时清空之前 setFields 注入的错误状态(因为 Control 在 reset 时会 updateFormItem 并清除 errors)。
与 clearFields 的对比
README.zh-CN.md 中说明:
clearFields:清除表单控件的值以及校验状态。等同于form.setFields({ [field]: { value: undefined; error: null, warning: null } })(版本2.29.0起)
这正好印证了 setFields 的 error: null、warning: null 可用来主动清除校验状态。
典型应用场景
场景一:服务端返回错误回填
后端接口校验失败时,把返回的错误信息通过 setFields 直接注入对应字段:
const [form] = Form.useForm();
async function handleSubmit() {
try {
await api.submit(form.getFields());
} catch (e) {
// 例如 e.errors = { email: '该邮箱已被注册' }
form.setFields({
email: { error: { message: e.errors.email } },
});
}
}
场景二:异步数据回填
表单数据为异步加载时,用 setFields 一次性设置值与校验状态。注意 README.zh-CN.md 的提醒:initialValue 只会在表单挂载时生效,异步数据请用 setFieldValue / setFieldsValue / setFields 更新。
useEffect(() => {
api.fetchUser().then((user) => {
form.setFields({
name: { value: user.name },
age: { value: user.age },
});
});
}, []);
场景三:组件挂载时机注意
README.zh-CN.md 常见问题中强调:若在 Modal 未挂载时调用 form.setFieldsValue({}) 等实例方法会不生效,因为此时 Form 尚未创建。同理适用于 setFields——调用时机需保证 Form 已挂载(或设置 Modal 的 mountOnEnter={false})。
边界行为与实现细节
- error 优先级高于 warning:
Form.Item的最终状态由 form-item.tsx 中的优先级决定——有 error 时显示error,否则有 warning 显示warning,两者都无且设置了help时展示 help 提示。 - warning 不阻塞提交:
RulesProps中的validateLevel: 'warning'不会阻塞表单提交;同理setFields写入的warning只作为非阻塞提示展示。 - 校验状态联动:
Control在gotoValidatingStatus中会先清空 errors/warnings 再进入validating状态,因此setFields注入的错误会在下次校验时被覆盖(详见 control.tsx)。 - 测试覆盖:仓库 components/Form/test/setfield.test.tsx 验证了
setFieldsValue对空字符串、空数组等值的收集行为;变更日志 components/Form/changelog/index.zh-CN.md 记录了setFields传入warning: null时未清空 warning 状态的修复、以及 formList 中setFields错误状态索引错位的修复,说明该方法在复杂表单(嵌套字段、Form.List)中同样受支持。
小结
setFields 是 Arco Design Form 在外部驱动表单项状态的核心方法:它一次调用即可完成"设值 + 注入错误 + 注入警告 + 标记 touched"四件事,是 setFieldValue / setFieldsValue 的能力超集。结合 resetFields、clearFields 与 Form.Item 的渲染机制,你可以完全在表单外部编排错误展示,实现服务端校验反馈、异步回填、联动校验等交互,而不必依赖用户操作触发的内置校验流程。