本篇文章将带您从了解formik到掌握,最后熟练使用。本篇文章的code您可以在gitee中获取:https://gitee.com/xifeng-canyang/jest-formik/tree/master/src/page/formik
- 何为formik
- 与Redux-Form比较
- 安装
- show Time
- 基本例子
- 校验
- yup校验
- Formik组件
- array or object formik
- 常用api
- ErrorMessage
- Field
- getIn()
- setValues()和setFieldValue()
- 案例
- 来一个简单的
- 再来一个难一点的
何为formik
什么是formik呢?Formik 是一小群 React 组件和钩子,用于在 React 和 React Native 中构建表单。它有助于解决三个最烦人的部分,帮助我们解决了表单中的三个核心问题:
- 从表单状态获取值和从表单状态获取值
- 验证和错误消息
- 处理表单提交
由此可知,Formik 将使事情井井有条——使测试、重构和推理表单变得轻而易举。
与Redux-Form比较
- Redux-Form 在每次按键时都会多次调用整个顶级 Redux 减速器。这对于小型应用程序来说很好,但是随着您的 Redux 应用程序的增长,如果您使用 Redux-Form,输入延迟将继续增加。
- Redux-Form 压缩压缩后大小为 22.5 kB(Formik 为 12.7 kB)
- Formik 的目标是创建一个可扩展、高性能的表单助手
安装
依赖包安装
npm install formik --save
cdn集成
<script src="https://unpkg.com/formik/dist/formik.umd.production.min.js"></script>
添加此内容后,您将可以访问变量window.Formik<Insert_Component_Name_Here>。
show Time
基本例子
先来看一个简单的表单例子,新建文件src\page\formik\com\basic-example.tsx:
import React from 'react';
import { useFormik } from 'formik';
import { validate1 } from './common/utils';
const SignupForm = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: '',
firstName: '',
lastName: '',
},
validate: validate1,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null} <br />
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null} <br />
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null} <br />
<button type="submit">Submit</button>
</form>
);
};
export default SignupForm;
解释:
- handleSubmit:提交处理程序
- handleChange<input>:传递给每个、<select>、 或的更改处理程序<textarea>
- values:我们表单的当前值
- handleChange我们为每个 HTML 输入重用相同的更改处理函数
- 我们传递一个id与我们定义的属性相匹配的nameHTML 属性initialValues
- 我们使用相同的名称(email-> formik.values.email)访问字段的值
假如我们没有认识formik这个东西,那么我们监测受控组件就得一个个写change事件。
校验
Formik 不仅跟踪您的表单values,还跟踪其验证和错误消息。要使用 JS 添加验证,让我们指定一个自定义验证函数并将其传递给validate钩子useFormik()。如果存在错误,这个自定义验证函数应该生成一个与我们的/error形状匹配的对象。
在前面的例子basic-example.tsx当中,我们引入了一个validate的文件,因此,我们需要新建文件:src\page\formik\com\common\utils.ts
export const validate1 = (values: any) => {
const errors = {} as any;
if (!values.firstName) {
errors.firstName = 'Required';
} else if (values.firstName.length > 15) {
errors.firstName = 'Must be 15 characters or less';
}
if (!values.lastName) {
errors.lastName = 'Required';
} else if (values.lastName.length > 20) {
errors.lastName = 'Must be 20 characters or less';
}
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;
};
默认情况下,Formik 将在每次击键(更改事件)、每个输入的模糊事件以及提交之前进行验证。校验确实实现了,但是如果我们希望在提交表单的时候再显示错误,需要怎么做呢?
Formik 会跟踪哪些字段已被访问过。它将这些信息存储在一个名为 touched 的对象中,同时也反映了values/initialValues的值,这些值为boolean值。
为了利用touched,我们传递formik.handleBlur给每个输入的onBlurprop。此函数的工作原理与它类似formik.handleChange,它使用name属性来确定要更新那个字段。
我们新建文件src\page\formik\com\basic-example-touched.tsx
import React from 'react';
import { useFormik } from 'formik';
import { validate1 } from './common/utils';
const SignupForm = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: '',
firstName: '',
lastName: '',
},
validate: validate1,
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
// onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null} <br />
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
// onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null} <br />
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
// onBlur={formik.handleBlur}
value={formik.values.email}
/>
{/* {formik.errors.email ? <div>{formik.errors.email}</div> : null} */}
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null} <br />
<button type="submit">Submit</button>
</form>
);
};
export default SignupForm;
yup校验
由于 Formik 作者/用户非常喜欢Yup,因此 Formik 有一个名为 Yup 的特殊配置道具validationSchema,它会自动将 Yup 的验证错误消息转换为一个漂亮的对象,其键匹配values
安装
npm install yup --save
我们新建文件:src\page\formik\com\basic-validate.tsx
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const SignupFormValidate = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: '1@qq.com',
firstName: 'david',
lastName: 'zhu',
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
}),
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name: </label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
//{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null} <br />
<label htmlFor="lastName">Last Name: </label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null} <br />
<label htmlFor="email">Email Address: </label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{/* {formik.errors.email ? <div>{formik.errors.email}</div> : null} */}
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null} <br />
<button type="submit">Submit</button>
</form>
);
};
export default SignupFormValidate;
yup的使用让我们看到了,功能不变的情况下代码可以更加的简洁。
Formik组件
以上我们所有的例子都是通过useFormik拿到我们控制formik表单的内容,但是这样不容易形成封装,也就是说我们无法进行实例传值。而Formik由于配备了React Context,因此Formik本身就可以管理所包裹的JSX
我们新建文件src\page\formik\com\basic-formik.tsx:
import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignupFormFormik = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps('firstName')}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null} <br />
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
type="text"
{...formik.getFieldProps('lastName')}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null} <br />
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps('email')} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null} <br />
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
};
export default SignupFormFormik;
以上,我们可以用formik独有的Field来简写
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const SignupFormFormikField = () => {
return (
<Formik
initialValues={{ firstName: '', lastName: '', email: '' }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" /> <br />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" /><br />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" /><br />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default SignupFormFormikField;
所以我们在明白了forimik是如何运行之后,后续的封装也是基于Formik来处理,会单独讲解。
前面的例子的values值都只是一个普通对象,如果这个对象是深度的,那么该如何处理呢
array or object formik
Formik 开箱即用地支持嵌套对象和数组
Formik 中的props name可以使用类似 lodash 的点路径来引用嵌套的 Formik 值。这意味着您不再需要展平表单的值。
我们新建文件src\page\formik\com\basic-array-obj.tsx:
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const BasicForArrayObj = () => {
return (
<Formik
initialValues={
{
firstName: '',
lastName: '',
email: '',
social: {
facebook: '',
twitter: '',
}
}
}
validationSchema={Yup.object({
social: Yup.object({
facebook: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
twitter: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
}),
firstName: Yup.string()
.max(15, 'Must be 15 characters or less')
.required('Required'),
lastName: Yup.string()
.max(20, 'Must be 20 characters or less')
.required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="twitter">facebook</label>
<Field name="social.twitter" type="text" />
<ErrorMessage name="social.twitter" /> <br />
<label htmlFor="facebook">facebook</label>
<Field name="social.facebook" type="text" />
<ErrorMessage name="social.facebook" /> <br />
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" /> <br />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" /><br />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" /><br />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default BasicForArrayObj;
注意:
- yup真丢对象校验使用Yup.object({})
- 访问属性值通过点属性字符串
同理数组也是类似的做法,我们新建文件src\page\formik\com\basic-array-obj1.tsx:
import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const BasicForArrayObj2 = () => {
return (
<Formik
initialValues={
{
friends: ['', ''],
}
}
validationSchema={Yup.object({
friends: Yup.array().of(Yup.string().required('Required')),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<label htmlFor="friends[1]">friend2</label>
<Field name="friends[1]" type="text" />
<ErrorMessage name="friends[1]" /> <br />
<label htmlFor="friends[0]">ftiend1</label>
<Field name="friends[0]" type="text" />
<ErrorMessage name="friends[0]" /> <br />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
export default BasicForArrayObj2;
注意:
- yup验证了每个数组内容,因此如果有一个没有值,将无法正常提交
- yup验证数组:https://github.com/jquense/yup#arrayoftype-schema-this
- 访问属性形式:friends[1]
常用api
ErrorMessage
捕捉错误的容器,相当于下列的简写,用法见上面的例子,必传一个name属性。
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null} <br />
Field
将自动将输入连接到 Formik,默认渲染是input输入框,他有以下几种方法渲染
直接渲染
<Field type="email" name="email" placeholder="Email" />
as转化成其他节点
<Field as="select" name="color">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>
渲染jsx
<Field name="lastName">
{({
field, // { name, value, onChange, onBlur }
form: { touched, errors }, // also values, setXXXX, handleXXXX, dirty, isValid, status, etc.
meta,
}) => (
<div>
<input type="text" placeholder="Email" {...field} />
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</div>
)}
</Field>
以component组件形式渲染
<Field name="lastName" placeholder="Doe" component={MyInput} />
getIn()
获取点属性的值,在values包含数组或者深层次对象的时候有用,可以对比上述案例。
setValues()和setFieldValue()
setValues()设置formik的values属性值,setFieldValue()设置values的某一个属性值,二者写法不同,但是目的一样:改变values的值。使用他们之前最好配套useFormik使用:
formik.setFieldValue('firstName','zhangsan');
formik.setValues({
email: '',
firstName: 'lisi',
lastName: '',
})
案例
主要讲解了如何封装formik,初始化formik,formik和Mui的使用,以及表单提交
来一个简单的
首先建立一个form.tsx ,他会接收一个formik实例,本质上用到了React.Provider,确保了在其包裹内可以使用useFormik进行获取对应的values值。
import type { FormikConfig,FormikValues,FormikContextType } from 'formik';
import type { ReactNode } from 'react';
import { FormikProvider,Form, } from 'formik';
import React from 'react';
interface FormProps<FormikValues> {
instance: FormikContextType<FormikValues>,
children: ReactNode
}
const ZLForm = <FormikValues extends object>(props: FormProps<FormikValues>) => {
const { instance,children } = props;
return (
<FormikProvider value={instance}>
<Form>
{children}
</Form>
</FormikProvider>
)
}
export default ZLForm;
然后建立一个功能组件wrapper.tsx
初始化formik
const testFormik = useFormik<Person>({
initialValues: {
username: 'jack',
age: 12
},
onSubmit:(values) => {
console.log('i am submit',values)
}
})
获取values
const { values,setFieldValue } = testFormik;
设置初始值,初始值如果不使用的话,便会使用默认的initialValues
useEffect(() => {
setFieldValue('username',initialValue.username);
setFieldValue('age',initialValue.age);
},[])
主要业务结构,这里需要注意使用受控组件,即表单的value值和他的onChange事件要对上来;当我们input框发生改变的时候,会触发testFormik.handleChange;type='submit'相当于表单form的submit,会触发formik的onSubmit事件
另外ZLInput是封装的组件,见gitee:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/component/common/Input.tsx
<ZLForm instance={testFormik}>
<Grid container>
<Grid item xs={12}>
<ZlInput name='username' label='username' value={values.username} handleInputChange={testFormik.handleChange} />
<ZlInput name='age' label='age' value={values.age} handleInputChange={testFormik.handleChange} />
</Grid>
<Grid item xs={12}>
<Button variant="contained" type='submit'>submit</Button>
</Grid>
</Grid>
</ZLForm>

完整的demo:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/page/formik/advance/wrapper.tsx
再来一个难一点的
难一点是指,我们的初始值不在是一个简单的对象,而是对象、数组、深层次对象和数组对象的结合体。本例子可以在gitee上找到:https://gitee.com/xifeng-canyang/jest-formik/blob/master/src/page/formik/advance/wrapper-complex.tsx
注意点:
- gitee代码是都写在了一个文件里面,因此显得业务比较臃肿,实际开发建议抽出公共组件,从而降低代码的复杂度
- 对于深层次的数据展示我们是使用点运算符
- 对于深层次数据的获取则是使用getIn
我们新建一个文件wrapper-complex
初始值示例
const testFormik = useFormik<Person>({
initialValues: {
username: 'jack',
age: 12,
like: {
sport: 'yes',
pingPong: 'no'
},
subject: ['math','english','chinese'],
subjectDetail: [
{
subject: 'math',
score: 80
},
{
subject: 'english',
score: 76
},
{
subject: 'chinese',
score: 95
}
]
},
onSubmit:(values) => {
console.log('i am submit',values)
}
})
简单的对象展示
<Grid item xs={12}>
<ZlInput name='username' label='username' value={values.username} handleInputChange={testFormik.handleChange} />
<ZlInput name='age' label='age' value={values.age} handleInputChange={testFormik.handleChange} />
</Grid>
深层次的对象展示
<Grid item xs={12}>
{
Object.keys(values.like).map(subkey => {
return (
<ZlInput key={subkey} name={`like.${subkey}`} label={getIn(values,`like.${subkey}`)} value={getIn(values,`like.${subkey}`)} handleInputChange={testFormik.handleChange} />
)
})
}
</Grid>
数组展示
<Grid item xs={12}>
{
values.subject.map((item,index) => {
return (
<ZlInput key={item} name={`subject[${index}]`} label={getIn(values,`subject[${index}]`)} value={getIn(values,`subject[${index}]`)} handleInputChange={testFormik.handleChange} />
)
})
}
</Grid>
数组对象结合展示
<Grid container>
{
values.subjectDetail.map((item,index) => {
const keyArr = Object.keys(item);
const uniKey = JSON.stringify(item);
return (
<Title key={uniKey}>
<Title>科目:{item.subject}</Title>
<ZlInput name={`subjectDetail[${index}].${keyArr[1]}`} label={getIn(values,`subjectDetail[${index}].${keyArr[1]}`)} value={getIn(values,`subjectDetail[${index}].${keyArr[1]}`)} handleInputChange={testFormik.handleChange} />
</Title>
)
})
}
</Grid>
效果图

看到这里也不容易,帮忙点点赞支持支持~