
Formik 表单提交全流程指南从 handleSubmit 到 isSubmitting 的生命周期管理【免费下载链接】formikBuild forms in React, without the tears 项目地址: https://gitcode.com/gh_mirrors/fo/formik导读在 React 应用中使用 Formik 构建表单时理解提交这一核心环节的生命周期至关重要。本文以 Formik 官方指南 docs/guides/form-submission.md 为主线完整剖析一次表单提交从触发到结束的三大阶段——预提交Pre-submit、验证Validation、正式提交Submission并结合 packages/formik/src/Formik.tsx 源码与 Formik.test.tsx 测试用例深入解释handleSubmit、submitForm、isSubmitting、isValidating、submitCount等状态的真实变化时机。读完本文你将能够精准控制提交按钮的禁用状态、防止重复提交、正确处理同步与异步提交并能在实际项目中排查isSubmitting 一直为 true之类的经典问题。触发提交的两种方式在 Formik 中提交表单本质上只有两条路径二者最终会执行相同的内部流程handleSubmit(e)由Formik渲染出的form onSubmit{handleSubmit}自动绑定或通过 Form 组件它是原生form的轻量封装自动接入handleSubmit与handleReset触发。它是用户点击提交按钮时最常走的路。submitForm()命令式imperative方法适合在表单之外如异步逻辑、定时器、其他组件的回调中主动触发一次提交返回一个 Promise。无论是哪条路径每次触发时 Formik 都会依次执行下面三个阶段官方以伪代码形式给出本文将逐一展开并对照源码。阶段一预提交Pre-submit在真正开始校验之前Formik 先做三件准备工作Touch 所有字段把touched对象中所有字段全部置为true。initialValues是必需的、必须始终指定这也是为什么缺少initialValues会引发警告甚至 React 的 uncontrolled 错误。将isSubmitting置为true从这一刻起表单进入提交中状态直到整个提交流程结束。submitCount加 1记录用户尝试提交的总次数。这三步操作在源码中对应formikReducer的SUBMIT_ATTEMPTaction见 Formik.tsxcase SUBMIT_ATTEMPT: return { ...state, touched: setNestedObjectValuesFormikTouchedValues( state.values, true ), isSubmitting: true, submitCount: state.submitCount 1, };可以看到setNestedObjectValues(state.values, true)会以当前values的形状为模板把每个字段的touched递归置为true——这就是touch 所有字段的确切实现即使字段是嵌套对象也会被完整覆盖。阶段二验证Validation预提交完成后进入验证阶段isValidating置为true。并行运行全部验证包括所有字段级验证Field/FastField/useField上的validate、表单级validate函数以及validationSchemaYup schema三者异步执行结果通过deepmerge深度合并成一个统一的errors对象。根据结果分流存在错误中止提交。isValidating置回falseerrors更新为校验结果isSubmitting置回falseonSubmit不会被调用。没有错误isValidating置回false进入正式提交阶段。源码中submitForm的实现Formik.tsx清楚地体现了这一逻辑const submitForm useEventCallback(() { dispatch({ type: SUBMIT_ATTEMPT }); return validateFormWithHighPriority().then( (combinedErrors: FormikErrorsValues) { const isInstanceOfError combinedErrors instanceof Error; const isActuallyValid !isInstanceOfError Object.keys(combinedErrors).length 0; if (isActuallyValid) { // Proceed with submit... let promiseOrUndefined; try { promiseOrUndefined executeSubmit(); // Bail if its sync, consumer is responsible for cleaning up if (promiseOrUndefined undefined) { return; } } catch (error) { throw error; } ... } else if (!!isMounted.current) { dispatch({ type: SUBMIT_FAILURE }); ... } } ); });validateFormWithHighPriorityFormik.tsx内部执行runAllValidations——用Promise.all并行运行runFieldLevelValidations、runValidationSchema、runValidateHandler再用deepmerge.all深度合并三份结果然后依次派发SET_ISVALIDATING: false和SET_ERRORS。关于三种验证的更多细节validate、validationSchema、字段级validate的写法与触发时机可参见 验证指南。阶段三正式提交Submission验证通过后Formik 调用你提供的提交处理函数onSubmithandleSubmit最终也会走到这里executeSubmit源码见 Formik.tsx实际就是onSubmit(state.values, imperativeMethods)。此阶段的关键行为取决于onSubmit是否返回 Promise返回 Promise异步提交Formik 会等待该 Promiseresolve 或 reject然后自动派发SUBMIT_SUCCESSresolve 时或SUBMIT_FAILUREreject 时将isSubmitting置回false。你不需要手动调用setSubmitting(false)。不返回 Promise同步提交Formik 检测到返回值是undefined后会直接退出见源码中if (promiseOrUndefined undefined) { return; }的分支此时必须在onSubmit末尾手动调用setSubmitting(false)否则isSubmitting将永远停留在true。case SUBMIT_SUCCESS: return { ...state, isSubmitting: false }; case SUBMIT_FAILURE: return { ...state, isSubmitting: false };这一设计意图在源码注释中写得很明确如果总是把同步提交也包进 Promise 并自动派发SUBMIT_SUCCESS那么isSubmitting会在渲染完成前就变回false导致提交按钮无法被禁用——这是所有基于回调/副作用编写的 v1 风格 Formik 代码所依赖的行为因此同步提交的清理责任被交还给了开发者。同步与异步 onSubmit 的完整示例异步提交推荐以下示例来自仓库中的 examples/async-submission/index.js展示了标准的异步写法onSubmit返回 PromiseFormik 自动管理isSubmitting同时按钮在提交期间被禁用import React from react; import ReactDOM from react-dom; import { Formik, Field, Form } from formik; const sleep (ms) new Promise((r) setTimeout(r, ms)); const Example () ( div h1Sign Up/h1 Formik initialValues{{ firstName: , lastName: , email: , }} onSubmit{async (values) { await sleep(500); alert(JSON.stringify(values, null, 2)); }} {({ isSubmitting }) ( Form label htmlForfirstNameFirst Name/label Field namefirstName placeholderJane / label htmlForlastNameLast Name/label Field namelastName placeholderDoe / label htmlForemailEmail/label Field nameemail placeholderjaneacme.com typeemail / button typesubmit disabled{isSubmitting} Submit /button /Form )} /Formik /div ); ReactDOM.render(Example /, document.getElementById(root));同步提交必须手动收尾当onSubmit是同步函数时必须手动调用setSubmitting(false)结束整个循环这也是 Formik API 参考 中onSubmit一节特别强调的行为Formik initialValues{{ name: jared }} onSubmit{(values, actions) { setTimeout(() { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); }, 1000); }} {props ( form onSubmit{props.handleSubmit} input typetext onChange{props.handleChange} onBlur{props.handleBlur} value{props.values.name} namename / {props.errors.name div idfeedback{props.errors.name}/div} button typesubmitSubmit/button /form )} /FormiksetSubmitting在源码中的定义非常直白Formik.tsxdispatch({ type: SET_ISSUBMITTING, payload: isSubmitting })它被包含在传给onSubmit的FormikHelpers中见 types.tsx。测试如何验证提交生命周期仓库中的单元测试 Formik.test.tsx 为上述行为提供了可复现的证据。例如submitForm触发时isSubmitting的翻转时序it(isSubmitting is fired when submit is attempted (v2, promise), async () { // 提交前 isSubmitting 为 false expect(getProps().isSubmitting).toBe(false); let submitFormPromise: Promiseany; act(() { submitFormPromise getProps().submitForm(); }); // 提交被尝试时 isSubmitting 立即变为 true expect(getProps().isSubmitting).toBe(true); await act(async () { await submitFormPromise; }); // Promise resolve 后自动回到 false expect(getProps().isSubmitting).toBe(false); });仓库还专门测试了异步错误不应破坏 Promise 链submitForm helper should not break promise chain if handleSubmit has returned rejected Promise见 Formik.test.tsx当onSubmitreject 时submitForm()返回的 Promise 同样会被 reject且isSubmitting通过SUBMIT_FAILURE正确复位。这意味着你可以在调用方用.catch()捕获提交失败而无需担心状态卡死。状态速查三个关键布尔值状态含义典型取值时机isSubmitting是否正在提交提交被尝试handleSubmit/submitForm调用时置true验证失败、onSubmit的 Promise resolve/reject、或同步提交中手动setSubmitting(false)后置falseisValidating是否正在验证validateForm或提交流程中的验证运行期间为true验证结束无论成败后为falsesubmitCount提交尝试次数每次handleSubmit/submitForm调用时 1resetForm后归零这三个字段都定义在 FormikState 中并通过FormikProps注入到你的渲染函数里。isValiderrors为空则为true、dirty与initialValues深度不等则为true也是提交场景中常用的计算属性。常见问题FAQ以下内容来自官方指南 docs/guides/form-submission.md 的 FAQ是实际开发中最常被问到的问题。如何判断我的提交处理函数正在执行当isValidating为false且isSubmitting为true时说明校验已经通过、onSubmit正在执行。这是区分正在验证与正在提交的关键判据。为什么 Formik 在提交前要 touch 所有字段业界常见做法是输入框的报错只在字段被访问过touched时才显示。提交前 Formik 将全部字段标记为 touched就是为了让所有之前被隐藏的错误此刻全部可见避免用户提交后面对没有报错却无法提交的困惑。这对应源码中SUBMIT_ATTEMPT里的setNestedObjectValues(state.values, true)。如何防止重复提交在isSubmitting为true时禁用触发提交的控件即可。最常用的做法就是上面示例中的button typesubmit disabled{isSubmitting}Submit/button。由于isSubmitting在SUBMIT_ATTEMPT阶段校验之前就被置为true即使onSubmit是异步的按钮也会在整个流程中保持禁用从根源上杜绝了双击提交。如何判断我的表单正在提交前验证当isValidating为true且isSubmitting为true时说明表单正处于提交前的验证阶段。你也可以用这个组合状态在界面上展示验证中的加载提示。为什么提交结束后isSubmitting仍然为 true这是新手最容易踩的坑官方给出了两条排查路径onSubmit返回了 Promise请确认该 Promise 最终会正确resolve 或 reject。如果 Promise 永远不落定比如网络请求没有超时处理Formik 会一直等待isSubmitting自然保持true。onSubmit是同步函数请确认在处理器末尾调用了setSubmitting(false)。正如前文源码分析所示同步提交时 Formik 会把清理责任交还给你。小结一次 Formik 提交可以概括为touch 所有字段 → 置isSubmitting为true、submitCount1 → 并行运行全部验证并深度合并 → 有错则中止复位无错则执行onSubmit→ 根据返回值Promise 自动收尾 / 同步需手动setSubmitting(false)结束循环。这套流程在 packages/formik/src/Formik.tsx 中以 reducer submitForm的形式清晰落地并由 Formik.test.tsx 中的测试用例逐条验证。掌握isSubmitting、isValidating与submitCount的状态语义你就能写出按钮禁用得当、防重复提交、同步/异步皆正确的生产级表单。【免费下载链接】formikBuild forms in React, without the tears 项目地址: https://gitcode.com/gh_mirrors/fo/formik创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考