前言
发现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库)来跟踪表单状态是不必要的。
- Redux-Form往往针对每一次用户击键多次调用前端系统顶层的Redux reducer。对于小应用来说,这还算可以;但是,随着你的Redux应用程序的不断增长,输入要求很可能会急剧膨胀——如果你使用Redux-Form作为前端表单解决方案的话。
- 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:
上述两种方式完全一样工作,内部实现原理完全相同。只是各自在使用风格上有所不同而已。请参考如下代码:
- //高阶组件方式
- 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>
- <h1>My Form</h1>
- <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>
- <h1>My Form</h1>
- <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,}) => (
- <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/