前文中我特别提起Redux Form以及redux-form的问题,我以为学习Formik你不得不提它们,固然还有它们的「老祖宗」React;既然选择了,那么你必须按照这个方向走下去。有一句叫做“没有最好,只有更好”。这句话应用于开源技术的学习上也很贴切,基于React技术的表单开发,到底哪种方案最好,相信国内外不少高手都在探讨这个问题。较早的redux-form这个自没必要说了,若是你选择并且热恋着redux,那么很不难不注意redux-form,可是redux-form尽管在必定程度上简化了基于redux的表单的开发可是也的确让人以为很辛苦,这一点从我本身追踪学习redux-form来讲就有深入体会。而如今的Formik开发者也正是体会到了这种痛苦,因而基于前辈们的做者又开发了Formik,并提供口号“Build forms in React, without the tears”——能不能浪漫一些翻译为“React表单不相信眼泪”?(受名片《莫斯科不相信眼泪》诱发)。总之一句话,只有先了解了开发复杂React表单的痛苦你才会深入体会学习的Formik的必要性。固然,Formik自己也很年轻,只有1岁,版本目前是1.0.2。可是,我相信这个库会很快地发展下去,除非短期内出现了比Formik更为优秀的React Form解决方案。react
<Field />会自动把表单中的输入字段「加入」到Formik系统中。它使用name属性匹配Formik中的状态( state)。 <Field />会默认对应一个HTML的 <input />元素。复杂情形下,你能够改变这个底层元素——这能够经过指定此API的component属性的方式实现(这些思路与redux-form都是一致的!)。在此,component属性值能够是一个简单的字符串,如“ select”,也多是另外一个复杂的React组件。固然, <Field /> 还拥有一个很重要的render属性。
下面的代码片段给出了<Field />及其重要属性(component属性和render属性)的典型应用展现。程序员
import React from 'react'; import { Formik, Field } from 'formik'; const Example = () => ( <div> <h1>My Form</h1> <Formik initialValues={{ email: '', color: 'red', firstName: '' }} onSubmit={(values, actions) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); }, 1000); }} render={(props: FormikProps<Values>) => ( <form onSubmit={props.handleSubmit}> <Field type="email" name="email" placeholder="Email" /> <Field component="select" name="color"> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </Field> <Field name="firstName" component={CustomInputComponent} /> <Field name="lastName" render={({ field /* _form */ }) => ( <input {...field} placeholder="firstName" /> )} /> <button type="submit">Submit</button> </form> )} /> </div> ); const CustomInputComponent: React.SFC< FieldProps<Values> & CustomInputProps > = ({ field, // { name, value, onChange, onBlur } form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc. ...props }) => ( <div> <input type="text" {...field} {...props} /> {touched[field.name] && errors[field.name] && <div className="error">{errors[field.name]}</div>} </div> );
思路是使用:validate?: (value: any) => undefined | string | Promise<any>redux
你能够经过把一个具备校验功能的函数传递给validate属性来执行独立的字段层面的校验。此功能的触发将会相应于在 <Field>的父级组件 <Formik>中指定的validateOnBlur 和validateOnChange配置选项,或者在withFormik方法调用中经过props指定的validateOnBlur 和validateOnChange这两个选项。固然,校验还会对应下面两种情形:数组
// Synchronous validation for Field const validate = value => { let errorMessage; if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) { errorMessage = 'Invalid email address'; } return errorMessage; };
请参考下面的代码:app
// Async validation for Field const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const validate = value => { return sleep(2000).then(() => { if (['admin', 'null', 'god'].includes(value)) { throw 'Nice try'; } }); };
【注意】考虑到i18n库(实现界面的多语言版本所须要)的使用方面,TypeScript检验类型比较宽松——容许你返回一个函数(例如i18n('invalid'))。异步
当你没有使用定制组件并且你想访问由<Field/>建立的底层的DOM结点(如调用focus)时,你能够经过把回调函数传递给innerRef属性来实现。ide
<FieldArray />这个API本质上是一个有助于实现字段数组或者列表操做的组件。你能够传递给它一个name属性——使其指向包含对应数组的values中的键所在路径。因而,<FieldArray />可让你经过render这个prop访问数组帮助方法(观察下面代码中的arrayHelpers)。 为了方便起见,调用这些方法就能够触发校验并能管理表单字段的touched信息。函数
import React from 'react'; import { Formik, Form, Field, FieldArray } from 'formik'; //下面提供的表单示例中有一个可编辑的列表。紧邻每个输入字段是控制插入与删除的按钮。 //若列表为空,那么会显示一个添加项目的按钮。 export const FriendList = () => ( <div> <h1>Friend List</h1> <Formik initialValues={{ friends: ['jared', 'ian', 'brent'] }} onSubmit={values => setTimeout(() => { alert(JSON.stringify(values, null, 2)); }, 500) } render={({ values }) => ( <Form> <FieldArray name="friends" render={arrayHelpers => ( <div> {values.friends && values.friends.length > 0 ? ( values.friends.map((friend, index) => ( <div key={index}> <Field name={`friends.${index}`} /> <button type="button" onClick={() => arrayHelpers.remove(index)} // remove a friend from the list > - </button> <button type="button" onClick={() => arrayHelpers.insert(index, '')} // insert an empty string at a position > + </button> </div> )) ) : ( <button type="button" onClick={() => arrayHelpers.push('')}> {/* show this when user has removed all friends from the list */} Add a friend </button> )} <div> <button type="submit">Submit</button> </div> </div> )} /> </Form> )} /> </div> );
这个属性指向values中相关联的键的名字或者路径。工具
默认值为true,用来决定在运行任何数组操做后执行仍是不执行表单校验。学习
你还能够遍历一个对象数组——经过使用一种格式为object[index]property或者是object.index.property的方法,它们分别做为<FieldArray />中的 <Field /> 或者<input />元素的name属性的值。请参考下面代码:
<Form> <FieldArray name="friends" render={arrayHelpers => ( <div> {values.friends.map((friend, index) => ( <div key={index}> <Field name={`friends[${index}]name`} /> <Field name={`friends.${index}.age`} /> // both these conventions do the same <button type="button" onClick={() => arrayHelpers.remove(index)}> - </button> </div> ))} <button type="button" onClick={() => arrayHelpers.push({ name: '', age: '' })} > + </button> </div> )} /> </Form>
当使用<FieldArray>时,进行校验有些值得注意的地方。
第一,若是你使用validationSchema,而且你的表单正好有数组校验需求 (例如一个最小长度值),以及在嵌套数组字段需求状况下,显示错误信息时也须要当心一些——Formik/Yup会在外部显示校验错误信息。例如:
const schema = Yup.object().shape({ friends: Yup.array() .of( Yup.object().shape({ name: Yup.string() .min(4, 'too short') .required('Required'), // these constraints take precedence salary: Yup.string() .min(3, 'cmon') .required('Required'), // these constraints take precedence }) ) .required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied .min(3, 'Minimum of 3 friends'), });
既然Yup和你的定制校验函数总会输出字符串形式的错误信息,那么你须要设法肯定在显示时是否你的嵌套错误信息是一个数组或者是一个字符串。
因而,为了显示“Must have friends”和“Minimum of 3 friends”(这是咱们示例中的数组校验约束)......
// within a `FieldArray`'s render const FriendArrayErrors = errors => errors.friends ? <div>{errors.friends}</div> : null; // app will crash
// within a FieldArray
's render
const FriendArrayErrors = errors => typeof errors.friends === 'string' ? <div>{errors.friends}</div> : null;
对于嵌套的字段错误信息而言,你应当假定并无预告定义对象的哪一部分——除非你事先检查了它。这样一来,你能够建立一个定制的<ErrorMessage />组件来帮助实现你的校验,此组件的代码相似以下:
import { Field, getIn } from 'formik'; const ErrorMessage = ({ name }) => ( <Field name={name} render={({ form }) => { const error = getIn(form.errors, name); const touch = getIn(form.touched, name); return touch && error ? error : null; }} /> );
//使用上面定制组件的情形: <ErrorMessage name="friends[0].name" />; // => null, 'too short', or 'required'
【注意】在Formik v0.12 / 1.0中,支持把一个新的meta属性添加给Field和FieldArray,此属性用于为你提供相似于error和touch这样的相关的元数据信息,这可使你免于不得不使用Formik或者lodash的getIn方法来检查是否你本身定义了路径部分。
下面的帮助函数能够经由render这个属性用来辅助操做字段数组:
有三种方式能够渲染<FieldArray />中包含的内容。请参考下面的代码:
import React from 'react'; import { Formik, Form, Field, FieldArray } from 'formik' export const FriendList = () => ( <div> <h1>Friend List</h1> <Formik initialValues={{ friends: ['jared', 'ian', 'brent'] }} onSubmit={...} render={formikProps => ( <FieldArray name="friends" render={({ move, swap, push, insert, unshift, pop }) => ( <Form> {/*... use these however you want */} </Form> )} /> /> </div> );
import React from 'react'; import { Formik, Form, Field, FieldArray } from 'formik' export const FriendList = () => ( <div> <h1>Friend List</h1> <Formik initialValues={{ friends: ['jared', 'ian', 'brent'] }} onSubmit={...} render={formikProps => ( <FieldArray name="friends" component={MyDynamicForm} /> /> </div> ); // 除去数组帮助函数,Formik中的状态和它自己的帮助函数 // (例如values, touched, setXXX, etc)都是经由表单的prop形式提供的 // export const MyDynamicForm = ({ move, swap, push, insert, unshift, pop, form }) => ( <Form> {/** whatever you need to do */} </Form> );
相似于<Field />, <Form />其实也是一个帮助性质的组件(helper component,用于简化表单编写并提升开发效率)。实际上,它是一个围绕<form onSubmit={context.formik.handleSubmit} />实现的包装器。这意味着,你不须要显式地书写<form onSubmit={props.handleSubmit} />——若是你不想干的话。
import React from 'react'; import { Formik, Field, Form } from 'formik'; const Example = () => ( <div> <h1>My Form</h1> <Formik initialValues={{ email: '', color: 'red' }} onSubmit={(values, actions) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); }, 1000); }} component={MyForm} /> </div> ); const MyForm = () => ( <Form> <Field type="email" name="email" placeholder="Email" /> <Field component="select" name="color"> <option value="red">Red</option> <option value="green">Green</option> <option value="blue">Blue</option> </Field> <button type="submit">Submit</button> </Form> );
此方法用于构建一个高阶React组件类,该类把props和form handlers (即"FormikBag")传递进你的根据提供的选项生成的组件中。
在你的内部表单组件是一个无状态函数组件状况下,你可使用displayName这个选项来给组件一个合适的名字——从而从React DevTools(调试工具,在我之前的博客中专门讨论过)中能够更容易地观察到它。若是指定了这个属性,你的包装表单中将显示成这样——Formik(displayName)。若是忽略这个属性,显示样式为Formik(Component)。不过,这个选项对于类组件(例如class XXXXX extends React.Component {..})并没必要要。
默认为false。这个属性用来控制当包装组件属性变化(使用深度相等比较,using deep equality)时Formik是否应当复位表单。
这是表单提交处理器。其中的参照是描述你的表单的values对象,还有“FormikBag”。其中,FormikBag:(a)包含一个对象,此对象拥有被注入的属性和方法的子集(如全部相似于set<Thing>格式的方法,还有方法resetForm);(b)包含任何属性——这些属性都将被传递给包装的组件。
默认为 false. 此选择用于控制表单加载前isValid属性的初始值。你还能够传递一个函数。 Useful for situations when you want to enable/disable a submit and reset buttons on initial mount.
If this option is specified, then Formik will transfer its results into updatable form state and make these values available to the new component as props.values. If mapPropsToValues is not specified, then Formik will map all props that are not functions to the inner component's props.values. That is, if you omit it, Formik will only pass props where typeof props[k] !== 'function', where k is some key.
Even if your form is not receiving any props from its parent, use mapPropsToValues to initialize your forms empty state.
【注意】Formik做者极力推荐使用validationSchema与Yup进行表单校验。可是,就校验这个任务而言,你能够任意选择本身喜欢的直观高效的校验方案。
使用函数校验表单的values对象。这个函数多是下面两种情形之一:
(A)同步函数,而且返回一个对象errors。 // Synchronous validation const validate = (values, props) => { let errors = {}; if (!values.email) { errors.email = 'Required'; } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) { errors.email = 'Invalid email address'; } //... return errors; };
(B)异步函数,它返回一个包含errors对象的Promise。
// Async Validation const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const validate = (values, props) => { return sleep(2000).then(() => { let errors = {}; if (['admin', 'null', 'god'].includes(values.username)) { errors.username = 'Nice try'; } // ... if (Object.keys(errors).length) { throw errors; } }); };
默认为true。在blur事件触发时(更具体一些说,是当调用handleBlur,setFieldTouched或者是setTouched时)使用这个选项进行校验。
默认为true。 经过此选项告诉Formik在change事件或者相关方法(更具体一些说,是当调用handleChange,setFieldValue或者setValues)触发时进行校验。
这个属性极为重要,它定义了一个Yup模式( schema)或者是返回Yup模式的一个函数。在校验是使用这个属性是很是有用的。错误信息被映射到内部组件的errors对象。它的键应当匹配values中对应的键。
这些与<Formik render={props => ...} />是一致的。
connect()是一个高阶组件,它用于把原始的Formik上下文以属性方式(命名为formik)注入到内部组件中。另一个有趣的事实是:Formik在底层上也利用了connect()来封装<Field/>,<FastField>和<Form>。所以,在开发定制组件时颇有经验的程序员可能会发现connect()仍是颇有用的。请参考下面的代码了解这个函数的基本用法:
import { connect } from 'formik'; const SubmitCount = ({ formik }) => <div>{formik.submitCount}</div>; export default connect(SubmitCount);