前言
发现Formik是在我学习redux-form过程中从国外一篇博客上偶然发现的,看到作者的高度肯定后我立即转到github上,正如许多朋友所关注的,Formik的星数达8282,这个数字在github虽然不算很高,但是从基于React技术跨平台表单开发这个主题角度来看,此数字已经相当可观了。不自觉地,我对比了redux-form与Formik的几个数据,如下:
库 | 开源库的时间 | 星数 |
---|---|---|
redux-form | 3年以前 | 10210 |
Formik | 1年前 | 8282 |
于是,我得出如下几个不太肯定的结论(欢迎有兴趣的朋友一起讨论):
1,redux-form是目前基于React+Redux开发构建表单子项目时主要开源选择方案; 2,redux-form很可能是目前大多数React程序员心目中高效解决方案的选择; 3,在github上Formik在未来一年后星数很有可能会超过redux-form项目。
我作出上述猜测主要理由是,经过这段时间我redux-form学习,我发现要深度掌握redux-form实践应用并灵活地处理各种有关问题,需要花费相当的代价。尤其说明问题的是,在表单体积膨胀和数量急剧增加的情况下,系统的性能有可能受到严重的影响。现在我有了react-redux基础,并理解了redux-form应用原理后,感觉使用Formik开发React表单一下变得异常轻松愉快!
为了节约时间,本系列的几篇我会首先使用英语方式,再在后面的时间里逐篇翻译成中文。
为什么不直接用Redux-Form?
至此,你可能会想:“为什么不使用Redux-Form这一方案呢?”对于这个问题,包括我在内的Redux用户都会自然地提出这个问题。对此,Formik开发者的列出如下三个理由:
1. React开发专家Dan Abramov认为,表单状态本质上是短暂的和局部性的,因此通过Redux解决方案(或任何类型的Flux库)来跟踪表单状态是不必要的。 2. Redux-Form往往针对每一次用户击键多次调用前端系统顶层的Redux reducer。对于小应用来说,这还算可以;但是,随着你的Redux应用程序的不断增长,输入要求很可能会急剧膨胀——如果你使用Redux-Form作为前端表单解决方案的话。 3. Redux-Form经压缩打包后的大小为22.5 kB,而Formik的大小为7.8 kB。
Formik的开发目标是:使用最小的易于使用的API调用创建一个可伸缩的、持久性的表单生成器。当然,还提供另外一些功能供开发者定制使用。
灵感来源
Formik受到Brent Jackson开发的高阶组件的启发 。同时还吸收了Redux-Form库的命名方案;还有受到React-Motion和React-Router 4的启发最新引入的render属性。无论你是否使用过上述这些库,Formik 都会使你在短短的数分钟内快速入门。
安装
把Formik添加到你的已有项目中的方式如下:
npm i formik --save
示例
官方网站上提供了一组类似于redux-form官方的示例供初学者学习之用。它们有:
- 基本类型示例
- 同步校验示例
- 开发自己的输入原型
- 与react-select联合应用
- 与Draft.js联合应用
- 访问React生命周期函数
- 在React Native开发中的应用
Formik的核心
Formik跟踪表单状态,然后以props方式把此状态还有一些可重用的方法和事件处理器(例如 handleChange,handleBlur和handleSubmit暴露给表单组件。其中,handleChange 和handleBlur工作方式完全一样——都使用name或者id属性来标记要更新的表单字段。 归纳来看,可以使用如下两种方式之一来使用Formik:
- withFormik():一个高阶组件(HoC),它接收一个配置对象作为参数;
- <Formik />:这是一个React组件,它提供了一个名称为render的属性。
上述两种方式完全一样工作,内部实现原理完全相同。只是各自在使用风格上有所不同而已。请参考如下代码:
//高阶组件方式
import React from 'react';
import { withFormik } from 'formik';
// Our inner form component which receives our form's state and updater methods as props
const InnerForm = ({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
//使用withFormik HoC包装表单
const MyForm = withFormik({
// Transform outer props into form values
mapPropsToValues: props => ({ email: '', password: '' }),
// Add a custom validation function (this can be async too!)
validate: (values, props) => {
const 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;
},
//表单提交处理器
handleSubmit: (
values,
{
props,
setSubmitting,
setErrors /* setValues, setStatus, and other goodies */,
}
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
// do whatevs...
// props.updateUser(user)
},
errors => {
setSubmitting(false);
// Maybe even transform your API's errors into the same shape as Formik's!
setErrors(transformMyApiErrors(errors));
}
);
},
})(InnerForm);
//然后你可以在任何地方自由使用<MyForm />组件
const Basic = () => (
<div>
My Form
<p>This can be anywhere in your application</p>
<MyForm />
</div>
);
export default Basic;
// Render Prop
import React from 'react';
import { Formik } from 'formik';
const Basic = () => (
<div>
My Form
<p>This can be anywhere in your application</p>
{/*
The benefit of the render prop approach is that you have full access to React's
state, props, and composition model. Thus there is no need to map outer props
to values...you can just set the initial values, and if they depend on props / state
then--boom--you can directly access to props / state.
The render prop accepts your inner form component, which you can define separately or inline
totally up to you:
- `<Formik render={props => <form>...</form>}>`
- `<Formik component={InnerForm}>`
- `<Formik>{props => <form>...</form>}</Formik>` (identical to as render, just written differently)
*/}
<Formik
initialValues={{
email: '',
password: '',
}}
validate={values => {
// same as above, but feel free to move this into a class method now.
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;
}}
onSubmit={(
values,
{ setSubmitting, setErrors /* setValues and other goodies */ }
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
// do whatevs...
// props.updateUser(user)
},
errors => {
setSubmitting(false);
// Maybe transform your API's errors into the same shape as Formik's
setErrors(transformMyApiErrors(errors));
}
);
}}
render={({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
/>
</div>
);
export default Basic;
补充说明
正你从上面观察到的,表单的校验逻辑完全由开发者自己实现。你可以心情使用第三方库来编写你自己的定制校验器。从官方网站上了解到,示例中大量使用Yup库来实现对象的格式校验。它提供了一种十分类似于Joi / React PropTypes的API,只不过在浏览器中尺寸十分小巧,且对运行时应用来说已经足够快。在此建议同学们也积极使用Yup。并且在Formik中也针对Yup提供了一种特别的配置选项/属性称作validationSchema,它能够把Yup的校验错误自动转换成一种很小的对象(对象中也提供了values和touched等键支持)。使用npm把Yup安装到你的项目中的方式如下:
npm install yup --save
参考资料
1.https://github.com/jaredpalmer/formik 2.http://www.lizhe.name/node/252 3.https://keyholesoftware.com/2017/10/23/the-joy-of-forms-with-react-and-formik/