官方示例工程formik-09x-synchronous-validation-example展示的是基于Formik的表单开发中如何进行定制的同步校验的问题。
回顾一下第一个示例Basics,其中有下面代码:
const EnhancedForm = withFormik({
mapPropsToValues: () => ({ email: '' }),
validationSchema: Yup.object().shape({
email: Yup.string()
.email('Invalid email address')
.required('Email is required!'),
}),
handleSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
},
displayName: 'BasicForm', // helps with React DevTools
})(MyInnerForm);
在上面代码片断中,理解mapPropsToValues属性的作用对于理解Formik全局有极为重要的意义。注意,在这里它返回了一个props对象(本例中是{ email: '' })。正是由于这一映射(即转换操作),才有了其他对应位置接下来的values.email(即props.values.email)引用。
其次,在实现表单数据校验方面,使用了下面代码:
validationSchema: Yup.object().shape({
email: Yup.string()
.email('Invalid email address')
.required('Email is required!'),
}),
这个validationSchema是使用Yup这个开源JS校验工具库所要求的。注意到,在判断一个字符串是否是一个有效的Email地址时,它使用了内置于Yup库的判断方式(调用email()方法进行判定——而并没有使用原始的正则表达式判定方式。
【问题】这里预留一个问题供朋友们思考:上面Yup校验是同步的还是异步的?
本文案例给出的正是Formik支持下的表单同步校验支持方案。关键代码如下:
const MyEnhancedForm = withFormik({
mapPropsToValues: () => ({ email: '' }),
// Custom sync validation
validate: values => {
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;
},
handleSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 1000);
},
displayName: 'BasicForm', // helps with React DevTools
})(MyForm);
其实,这里关键是理解validate这个属性(withFormik()方法的参数是一个可配置对象,validate则是该对象的属性之一)。根据官方文件介绍,这个属性是实现定制同步校验编程的关键思路。而且注意到,上面代码中使用非常原始的正则表达式方案校验电子邮件地址格式正确与否。
如果把这里的思路放开一些,即可以通过判定values.email,values.someotherfield......来进行其他众多的字段内容校验判定。也就是由于这一思路,我们注意到没有必要再和redux-form中那样实现专门的字段级别校验了。
尽管官方没有给出完整独立的表单异步校验案例,但是官方文档再介绍withFormik的validate属性用法时一并提供了同步校验和异步校验的例子,不过都是使用了定制校验方式:
// 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;
};
// 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;
}
});
};
其实,Formik作者极力推荐的Yup方案(结合validationSchema的使用)就是一种极其简洁的异步校验方案。当然,示例Basics中的代码比较隐蔽;更直观的异步校验代码片段如下:
const numSchema = yup.number();
const validator = (val) => {
numSchema.validate(val)
.then(result => {
console.log(result); // it is the value of `val`
return true;
})
.catch(error => {
console.log(error.errors); // array of validation error messages
return false;
});
};
相信你能够轻松地把上面代码片段修改后添加到withFormik这个HOC函数代码当中。
1,著名开源JS校验工具Yup(https://github.com/jquense/yup);
2,https://github.com/jaredpalmer/formik#validationschema-schema--props-props--schema
3,https://til.hashrocket.com/posts/35a5bwlxn7-yup-schemas-are-validated-asynchronously