Formik 错误消息渲染组件 `<ErrorMessage />` 完整指南:从 Props 到源码实现
Formik 错误消息渲染组件ErrorMessage /完整指南从 Props 到源码实现【免费下载链接】formikBuild forms in React, without the tears 项目地址: https://gitcode.com/gh_mirrors/fo/formikErrorMessage /是 Formik 中用于渲染字段错误消息的专用组件。它的核心职责是当某个字段已被访问过touched[name] true且存在错误error时渲染该字段的错误提示否则渲染null。本文将围绕 errormessage.md 文档结合packages/formik/src/ErrorMessage.tsx源码、packages/formik/test/ErrorMessage.test.tsx测试用例与utils.ts中的工具函数全面讲解它的使用方式、全部 Props、渲染优先级与底层实现原理。为什么需要ErrorMessage /在 Formik 中手动渲染错误消息需要同时判断errors和touched两个状态且必须在render props中反复编写条件表达式。例如{errors.name touched.name ? div{errors.name}/div : null}这种写法的问题在于每个字段都要重复编写「已访问且存在错误」的判断逻辑errors.name与touched.name这类取值方式对嵌套字段如social.facebook不友好逻辑分散在模板各处难以统一维护。ErrorMessage /正是为解决这一问题而设计它把「判断touched 读取error 决定渲染方式」这三件事封装成一个声明式组件与Field /、FastField /、FieldArray /一样支持 lodash 风格的点路径dot path与括号语法bracket syntax来定位嵌套字段。基本用法与迁移示例原文档给出了一个「将手写错误判断迁移到ErrorMessage /」的 diff 示例。完整的可运行版本如下该示例中的校验逻辑与 SchemaValidation.js 同出一辙均基于 Yupimport React from react; import { Formik, Form, Field, ErrorMessage } from formik; import * as Yup from yup; const SignupSchema Yup.object().shape({ name: Yup.string() .min(2, Too Short!) .max(70, Too Long!) .required(Required), email: Yup.string() .email(Invalid email) .required(Required), }); export const ValidationSchemaExample () ( div h1Signup/h1 Formik initialValues{{ name: , email: , }} validationSchema{SignupSchema} onSubmit{values { // same shape as initial values console.log(values); }} {({ errors, touched }) ( Form {/* 迁移前手写判断 */} {/* {errors.name touched.name ? div{errors.name}/div : null} */} {/* 迁移后一行搞定 */} ErrorMessage namename / Field nameemail typeemail / {/* 迁移前{errors.email touched.email ? div{errors.email}/div : null} */} {/* 迁移后一行搞定 */} ErrorMessage nameemail / button typesubmitSubmit/button /Form )} /Formik /div );从 diff 中可以看到ErrorMessage /在默认情况下不传component会直接返回错误消息字符串React 16 会将其作为文本节点渲染。这让{errors.name touched.name ? div{errors.name}/div : null}这样的三行代码被压缩成一行ErrorMessage namename /。渲染条件touched 与 error 双重要求原文档明确指出只有当字段被访问过touched[name] true且存在错误消息时ErrorMessage /才会渲染内容。这一点在源码中有直接体现。在 ErrorMessage.tsx 的render()方法中render() { let { component, formik, render, children, name, ...rest } this.props; const touch getIn(formik.touched, name); const error getIn(formik.errors, name); return !!touch !!error ? render ? isFunction(render) ? render(error) : null : children ? isFunction(children) ? children(error) : null : component ? React.createElement(component, rest as any, error) : error : null; }关键点使用getIn(formik.touched, name)与getIn(formik.errors, name)读取字段状态因此天然支持嵌套路径详见下文「嵌套字段」一节!!touch !!error是唯一的渲染开关两者缺一不可未满足条件时直接返回null不会渲染任何 DOM。这一行为也由 ErrorMessage.test.tsx 中的测试用例验证先用setFieldError(email, message)单独设置错误、不设置 touched此时actual仍为undefined测试注释明确写着 Only renders if Field has been visited.随后同时调用setFieldTouched(email)与setFieldError(email, message)错误消息才被渲染出来。关于「错误必须是字符串」的约定原文档要求ErrorMessage /期望给定字段的错误消息以字符串形式存储。这一点与 Formik 的校验约定一致——在 Formik 中errors对象的值默认约定为字符串Yup 的min/max/required/email等方法返回的正是字符串消息。如果你的校验函数返回的是对象、数组等非字符串结构例如某些第三方 schema 库ErrorMessage /会原样渲染该值无法保证展示效果建议在传入前先将其归一化为字符串。Props 参考以下 Props 定义与 ErrorMessage.tsx 中的ErrorMessageProps接口一一对应。name必填类型string必填项指定 Formik 状态中字段的名称。要访问嵌套对象或数组name支持 lodash 风格的路径写法ErrorMessage namesocial.facebook / ErrorMessage namefriends[0].firstName /底层实现中路径解析由utils.ts中的getIn完成。getIn 借助 lodash 的toPath将字符串路径解析为数组再逐层深入对象取值export function getIn(obj: any, key: string | string[], def?: any, p: number 0) { const path toPath(key); while (obj p path.length) { obj obj[path[p]]; } if (p ! path.length !obj) { return def; } return obj undefined ? def : obj; }这也是ErrorMessage /能与Field /、FastField /使用完全一致的路径语法访问嵌套字段的原因。children类型(message: string) React.ReactNode一个返回合法 React 元素的函数仅在字段被 touched 且存在错误时被调用并在后续状态更新时再次触发。// 回调只有在字段被 touched 且存在错误时才会被调用 ErrorMessage nameemail{msg div{msg}/div}/ErrorMessage注意这里的children是函数式render props写法而不是普通的子元素如果传入非函数子元素源码中isFunction(children)判断失败会返回null。component类型string | React.ComponentTypeFieldProps可以是 React 组件也可以是 HTML 元素的名称。若不指定ErrorMessage /将直接返回字符串。ErrorMessage componentdiv nameemail / // -- {touched.email error.email ? div{error.email}/div : null} ErrorMessage componentspan nameemail / // -- {touched.email error.email ? span{error.email}/span : null} ErrorMessage component{Custom} nameemail / // -- {touched.email error.email ? Custom{error.email}/Custom : null} ErrorMessage nameemail / // 将返回一个字符串。React 16。 // -- {touched.email error.email ? error.email : null}从源码可见component分支通过React.createElement(component, rest, error)创建元素错误消息字符串作为其 children传入其余 props如id、className等会展开到该元素上。id类型string用于给错误元素设置 DOM id主要用于 e2e 测试定位 DOM 元素对组件实现逻辑没有任何影响可以省略。// id 仅用于测试目的不参与核心实现 ErrorMessage nameemail idform_email_id /源码中id属于rest中的属性当component存在时会通过React.createElement(component, rest, error)透传到最终元素上当不传component纯字符串模式时它自然无处安放因此文档强调它「不 contribute anything to the core implementation」。render类型(error: string) React.ReactNode与children类似的函数式渲染接口仅在字段被 touched 且存在错误时被调用。ErrorMessage nameemail render{msg div{msg}/div} /渲染优先级render → children → component → 字符串结合render()方法可以得出一个清晰的优先级链。当touched与error同时满足时render若传入了render且是函数调用render(error)并返回其结果children否则若传入了函数式children调用children(error)component否则若指定了component用React.createElement(component, rest, error)创建元素兜底以上均未满足时直接返回错误字符串error。也就是说render的优先级最高children次之component再次最后是纯字符串输出。如果你同时传了多个渲染 prop实际生效的只有一个优先级最高者。另外ErrorMessageProps还预留了className?: string类型见 ErrorMessage.tsx它同样会通过rest透传给component指定的元素方便直接控制错误消息的样式。源码连接机制connect 与 FormikContextErrorMessage /本身并不直接读取 Formik 状态而是通过connect高阶组件注入formik上下文。文件末尾export const ErrorMessage connectErrorMessageProps, ...(ErrorMessageImpl);connect.tsx 的实现要点内部渲染FormikConsumer即 Formik 的 Context Consumer获取formik对象若 Context 为 undefined会抛出提示要求组件必须渲染在Formik组件内部将formik作为额外 prop 注入包装组件并保留WrappedComponent引用与静态属性hoistNonReactStatics。因此使用ErrorMessage /的前提是它必须位于Formik组件树之内这一点与Field /、FastField /、FieldArray /完全一致。性能优化shouldComponentUpdateErrorMessageImpl还实现了shouldComponentUpdate见 ErrorMessage.tsx这是一种内置的性能优化shouldComponentUpdate(props) { if ( getIn(this.props.formik.errors, this.props.name) ! getIn(props.formik.errors, this.props.name) || getIn(this.props.formik.touched, this.props.name) ! getIn(props.formik.touched, this.props.name) || Object.keys(this.props).length ! Object.keys(props).length ) { return true; } else { return false; } }当且仅当以下任一情况发生时组件才会重新渲染该字段对应的errors值发生变化该字段对应的touched值发生变化props 的键集合数量发生变化即外部传入的 props 被增删。这意味着即使 Formik 上下文中其他无关字段频繁更新ErrorMessage /也会通过「浅比较 按 name 定向读取」过滤掉无关重渲染保持渲染行为的精准与高效。这正是它不同于「在 render props 里手动判断」方案的又一层价值手写判断每次表单状态变化都会执行而ErrorMessageImpl可以自行决定是否进入render()。与 Yup 校验 Schema 的组合实战回到文章开头的示例ErrorMessage /最常见的搭配就是 Yup 校验 Schema。当用户提交或失焦触发校验后Yup 产生的错误消息Too Short!、Too Long!、Required、Invalid email等会被写入 Formik 的errors而touched由字段交互驱动。二者同时满足时ErrorMessage /立即渲染对应提示。在 examples/ 目录的多个示例中都能看到这一模式的延伸例如field-level-validation中字段级校验配合错误展示、instant-feedback中的即时反馈场景都是「校验产生 errors 交互产生 touched」再由ErrorMessage /或等价逻辑统一呈现。小结能力说明渲染条件touched[name] true且errors[name]存在二者缺一不可路径支持lodash 风格 dot pathsocial.facebook与 bracket 语法friends[0].firstName渲染方式render函数 children函数 component元素 纯字符串错误类型约定错误消息应为字符串上下文要求必须位于Formik组件树内通过connect Context 注入formik性能特性shouldComponentUpdate按字段定向比较errors/touched避免无关重渲染ErrorMessage /是 Formik 表单体系中「错误展示」环节的标准答案它把条件判断、嵌套取值、渲染策略与性能优化全部封装在内让开发者只需关心「展示什么」而不必重复编写「何时展示」。结合 ErrorMessage.test.tsx 的测试用例与 ErrorMessage.tsx 的源码你可以放心地将它作为表单错误展示的统一入口。【免费下载链接】formikBuild forms in React, without the tears 项目地址: https://gitcode.com/gh_mirrors/fo/formik创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考