- 前端
- UI组件
【免费下载链接】react-final-form
🏁 High performance subscription-based form state management for React
导读
本文围绕 react-final-form 官方示例 submission-errors 展开,深入讲解如何在表单提交失败时向用户展示提交错误(Submission Errors):包括"提交回调返回的错误对象应通过resolve返回而非reject抛异常"这一核心约定、字段级提交错误与表单级提交错误(FORM_ERROR)两种错误通道,以及meta.submitError、submitError、submitFailed、submitSucceeded、submitting等状态字段在渲染层与源码层的完整工作机制。读完本文,你将能够独立实现一个带用户名校验、密码校验、登录失败提示和重置功能的健壮登录表单,并理解提交错误与验证错误在 react-final-form 内部如何区分与流转。
一、示例背景与核心结论
该示例对应仓库目录 examples/submission-errors,是一份可运行的登录表单 Demo,包含 index.js、Styles.js 与 package.json 三个文件,依赖react-final-form@6.5.3、final-form@4.20.4与styled-components。
示例页面提示"Only successful credentials areerikrasandfinalformrocks",即只有用户名erikras与密码finalformrocks组合才能登录成功。其演示的核心要点可浓缩为一句话:
提交失败时,
onSubmit应当resolve(正常兑现)一个错误对象,而不是reject(拒绝)一个异常;reject仅保留给通信层或服务器异常使用。
这一约定保证了 react-final-form 能将错误对象中的信息解析到submitError/submitErrors状态中,进而驱动界面渲染,而不是把 Promise 拒绝当成未捕获异常处理。
二、完整可运行示例代码
以下是 examples/submission-errors/index.js 的完整实现,它同时演示了字段级提交错误、表单级提交错误、字段级验证错误与提交状态管理:
import React from "react"; import { render } from "react-dom"; import Styles from "./Styles"; import { Form, Field } from "react-final-form"; import { FORM_ERROR } from "final-form"; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const onSubmit = async (values) => { await sleep(300); // 模拟网络请求耗时 if (values.username !== "erikras") { return { username: "Unknown username" }; // 字段级提交错误 } if (values.password !== "finalformrocks") { return { [FORM_ERROR]: "Login Failed" }; // 表单级提交错误 } window.alert("LOGIN SUCCESS!"); }; const App = () => ( <Styles> <h1>React Final Form Example</h1> <h2>Submission Errors</h2> <a href="https://final-form.org/react" target="_blank" rel="noopener noreferrer" > Read Docs </a> <div> Only successful credentials are <code>erikras</code> and{" "} <code>finalformrocks</code>. </div> <Form onSubmit={onSubmit} validate={(values) => { const errors = {}; if (!values.username) { errors.username = "Required"; } if (!values.password) { errors.password = "Required"; } return errors; }} render={({ submitError, handleSubmit, form, submitting, pristine, values, }) => ( <form onSubmit={handleSubmit}> <Field name="username"> {({ input, meta }) => ( <div> <label>Username</label> <input {...input} type="text" placeholder="Username" /> {(meta.error || meta.submitError) && meta.touched && ( <span>{meta.error || meta.submitError}</span> )} </div> )} </Field> <Field name="password"> {({ input, meta }) => ( <div> <label>Password</label> <input {...input} type="password" placeholder="Password" /> {meta.error && meta.touched && <span>{meta.error}</span>} </div> )} </Field> {submitError && <div className="error">{submitError}</div>} <div className="buttons"> <button type="submit" disabled={submitting}> Log In </button> <button type="button" onClick={form.reset} disabled={submitting || pristine} > Reset </button> </div> <pre>{JSON.stringify(values, 0, 2)}</pre> </form> )} /> </Styles> ); render(<App />, document.getElementById("root"));代码中import { FORM_ERROR } from "final-form"直接从底层表单引擎final-form导入表单级错误专用键,这一用法在仓库中仅出现在 examples/submission-errors/index.js 一处,属于该示例的标志性手法。
三、错误返回机制:resolve 而不是 reject
示例文档用一句话点明了整个机制的设计意图:
Notice that the
Promiseshouldresolveto the submission error (not reject). Rejection is reserved for communications or server exceptions.
翻译与拆解如下:
- 错误即数据:提交失败并不是"异常流程",而是表单业务中一种正常的返回值。
onSubmit是async函数,当校验失败时直接return一个错误对象,等价于Promise.resolve(errorObject)。 reject的边界:reject语义被保留给"通信失败"或"服务器抛出的异常"。例如网络断开、HTTP 500、后端未预期的异常等。这类情况通常无法映射到具体的字段错误,也不适合直接作为submitError展示。- 为什么这样设计:react-final-form 依赖
onSubmit的兑现结果来更新表单状态。若onSubmit被reject,表单无法从中提取出结构化的错误对象来填充submitErrors,提交状态将停留在未明确的错误路径上;而resolve一个对象则让状态机可以区分"提交成功"、"提交失败(带字段错误)"、"提交失败(带表单级错误)"三种结局。
在 src/ReactFinalForm.tsx 中可以看到,handleSubmit最终调用form.submit()(见 src/ReactFinalForm.tsx#L156-L168),提交行为交由底层final-form引擎执行;onSubmit的返回值及其 Promise 兑现结果决定了submitFailed、submitSucceeded、submitError、submitErrors等状态的落定方向。
三种错误对象形态
根据返回值结构的不同,react-final-form 支持三种错误呈现通道:
| 返回内容 | 错误通道 | 示例中的演示 |
|---|---|---|
{ username: "Unknown username" } | 字段级提交错误,写入该字段的meta.submitError | 用户名不等于erikras时 |
{ [FORM_ERROR]: "Login Failed" } | 表单级提交错误,写入表单渲染 props 的submitError | 密码不等于finalformrocks时 |
无返回 / 返回undefined | 提交成功 | 凭据正确,弹出LOGIN SUCCESS! |
注意:字段级提交错误与验证错误(validate返回的error)并存于同一个字段上,但语义不同。示例代码在用户名输入框上的展示逻辑刻意写成(meta.error || meta.submitError) && meta.touched,即验证错误优先显示,其次才显示提交错误,且仅在字段被触摸(touched)之后才渲染——这与 src/useField.ts 中通过addLazyFieldMetaState将error、submitError等字段挂载到meta上的实现一致(见 src/getters.ts#L47-L71)。
四、字段级提交错误:meta.submitError
字段级提交错误的完整链路如下:
onSubmit返回{ username: "..." }这类以字段名(支持点路径如user.name)为键的错误对象;- 底层
final-form引擎将对应字段的错误写入该字段的submitError状态; - react-final-form 的
Field/useField通过订阅拿到新的字段状态,把submitError暴露到meta.submitError; - 组件在渲染层根据
meta.submitError展示提示。
在 src/getters.ts#L47-L71 的addLazyFieldMetaState中可以看到,meta上挂载了submitError、submitFailed、submitSucceeded、submitting、error、touched、dirty等全套字段级状态;而 src/useField.ts#L149 在字段尚未注册的初始状态下也将submitError: undefined作为默认值参与初始化。字段级状态还包含dirtySinceLastSubmit、modifiedSinceLastSubmit等与"上次提交后是否变更"相关的状态,供复杂的提交后校验场景使用。
示例中用户名输入框的渲染条件是(meta.error || meta.submitError) && meta.touched,这是官方推荐的组合用法:meta.error对应validate函数返回的验证错误,meta.submitError对应onSubmit返回的提交错误,两者结合可以在同一位置同时处理"必填"与"用户名不存在"两种错误。
五、表单级提交错误:FORM_ERROR 与 submitError
当错误无法归属到某个具体字段(例如"用户名密码不匹配"这种整体性错误)时,示例示范了用FORM_ERROR键承载表单级错误:
return { [FORM_ERROR]: "Login Failed" };FORM_ERROR是final-form导出的特殊键。该错误不会出现在任何字段的meta上,而是被写入表单级状态,并通过渲染 props 暴露为submitError。示例中表单顶部的展示代码如下:
{submitError && <div className="error">{submitError}</div>}在表单级状态这一侧,src/getters.ts#L16-L45 的addLazyFormState为<Form>的渲染 props 挂载了包括submitError、submitErrors、submitFailed、submitSucceeded、submitting、hasSubmitErrors、hasValidationErrors、dirtySinceLastSubmit等在内的完整状态集合。其中:
submitError:表单级提交错误(即FORM_ERROR对应的值);submitErrors:本次提交返回的完整错误对象(字段错误与表单级错误的并集);hasSubmitErrors:是否存在任意提交错误,可用于整体判断;hasValidationErrors:是否存在验证错误,与提交错误相互独立。
从源码结构可以推断:验证错误(validate阶段产生)与提交错误(onSubmit阶段产生)在底层final-form引擎中是两套独立的错误通道,分别通过errors与submitErrors承载,界面上可以按需分别或合并展示。示例中用户名框合并展示、密码框仅展示验证错误、表单顶部仅展示表单级提交错误,正是这两套通道解耦的直观体现。
六、表单级状态字段速查
结合示例渲染 props 解构出的submitError、handleSubmit、form、submitting、pristine、values,以及表单级状态的完整定义,汇总常用字段如下:
| 字段 | 含义 | 示例中的使用 |
|---|---|---|
handleSubmit | 提交处理器,绑定到<form onSubmit> | 驱动onSubmit流程 |
submitError | 表单级提交错误(FORM_ERROR值) | 顶部.error提示 |
submitting | 是否正在提交(提交期间为true) | 提交按钮disabled={submitting} |
pristine | 表单值是否与初始值一致 | 重置按钮disabled={submitting \|\| pristine} |
values | 当前表单值 | 底部<pre>实时预览 |
form.reset | 重置表单到初始状态 | Reset 按钮的onClick |
submitFailed/submitSucceeded | 上次提交是否失败 / 成功 | 可用于结果提示 |
hasSubmitErrors/hasValidationErrors | 是否存在提交 / 验证错误 | 整体错误判断 |
dirty/dirtySinceLastSubmit | 是否被改动 / 提交后是否改动 | 保存状态类提示 |
示例中form.reset的调用方式值得注意:在 src/ReactFinalForm.tsx#L170-L183 中,<Form>渲染 props 里的form.reset被包装为可接收 React 合成事件(SyntheticEvent)的版本——若传入的是事件对象则无参调用form.reset(),否则把值透传给form.reset(eventOrValues),因此在 JSX 中直接写onClick={form.reset}是安全且推荐的。
七、提交状态生命周期与测试验证
示例中的submitting状态贯穿提交全流程:点击 Log In 后按钮进入禁用态,onSubmit内部的await sleep(300)模拟了网络耗时,Promise 兑现后submitting恢复为false。这一行为在 src/ReactFinalForm.test.js 中有对应测试覆盖:
- src/ReactFinalForm.test.js#L876 附近:"should set submitting back to false after submit",验证提交完成后
submitting复位; - src/ReactFinalForm.test.js#L994 与 src/ReactFinalForm.test.js#L1027 附近:针对 issue #903,验证当
onSubmit立即返回Promise.resolve()时submitting同样会先置true再回到false,避免按钮永久禁用。
这些测试佐证了提交状态机的两个关键事实:其一,提交期间submitting必然经历false → true → false的完整翻转;其二,无论onSubmit同步返回还是异步兑现,状态复位逻辑都成立——这正是不用reject而用resolve约定得以稳定工作的基础。
同时,src/useField.test.js#L52 处对meta.submitError初始为undefined的断言,印证了字段级提交错误在未提交前不存在的默认语义。
八、运行方式与扩展建议
示例的依赖声明在 examples/submission-errors/package.json 中,使用react-final-form@6.5.3、final-form@4.20.4、react、react-dom与styled-components,入口为index.js。可在本地创建基于该文件结构的项目后执行npm install与npm start运行验证;官方示例同样支持在 CodeSandbox 中直接打开体验。
基于该示例可以自然延伸的实战能力包括:
- 提交错误清除策略:结合
dirtySinceLastSubmit或modifiedSinceLastSubmit,在用户修改字段后自动清除对应的提交错误,避免错误提示残留到下一次输入; - 整表提交错误:改用
FORM_ERROR承载"服务器校验失败"的整体性消息,与字段级错误分层展示; - 提交后重校验:利用
submitErrors与errors两套通道的组合,实现服务端校验结果与客户端validate结果的无缝合并展示。
总结
提交错误处理是 react-final-form 实战中绕不开的核心场景。本示例用 30 余行onSubmit与两个Field讲清楚了三条关键约定:错误通过 Promise resolve 返回、字段级错误写入meta.submitError、表单级错误写入FORM_ERROR并暴露为submitError。配合 src/ReactFinalForm.tsx、src/useField.ts 与 src/getters.ts 的源码实现,你可以在此基础上构建任意复杂的登录、提交与校验联动逻辑。
- 前端
- UI组件
【免费下载链接】react-final-form
🏁 High performance subscription-based form state management for React
相关推荐
UI-Router resolve错误处理实例:用户提示实现
UI Router resolve错误处理实例:用户提示实现 你是否遇到过这样的情况:用户点击页面后长时间无响应,控制台却显示"resolve失败"?在Angu
前端路由如何向Perspective加载数据:pandas、Polars、PyArrow与CSV/JSON全类型支持实战
如何向Perspective加载数据:pandas、Polars、PyArrow与CSV/JSON全类型支持实战 Perspective 是一个专为 大数据与流
数据可视化数据分析流处理WebAssembly图表库form-create表单提交策略:异步提交与错误处理最佳实践
form create表单提交策略:异步提交与错误处理最佳实践 引言:表单提交的痛点与解决方案 你是否还在为表单提交时的用户体验问题烦恼?表单提交过程中,用户常
低代码前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考