Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

最近的一些编程实践-react表单篇 #22

Open
Jeffersondyj opened this issue May 25, 2019 · 0 comments
Open

最近的一些编程实践-react表单篇 #22

Jeffersondyj opened this issue May 25, 2019 · 0 comments

Comments

@Jeffersondyj
Copy link
Owner

是的,表单篇,后续还会有姐妹篇-列表篇

本篇主要讲用formik+yup来解决表单中的大部分问题,减少工程师的代码量,让精力集中在业务上

需求

首先还是用需求来讲这个sample,需求本身不是很复杂,甚至是一个常见的简单需求:

image

点了第一个edit按钮时:

image

点了下面那个edit按钮时:

image

注意到联系人可以添加多个。而且2个提交都是提交整个的表单数据,即中心名称和联系人list。

formik初探

本来也可以用一个Input 一个TODOlist形式的组件来搞,然后维护state: {name, contactList: [{name, email}]}。每个input的onChange都去改state

不过当我看见formik的各种布道者安利说:“Build forms in React, without the tears”——“用formik做React表单不相信眼泪”时,有点心动了。于是我们来看:

`
import {compose, keys, pick} from 'ramda';
import {withFormik} from 'formik';
import * as yup from 'yup';
import * as validators from './validator';

const selectors = {
    contactList: contactSelector,
    name: makeMcSelector('name')
};
const formikOption = {
    mapPropsToValues: pick(keys(selectors)),
    validationSchema: yup.object().shape(validators),
    handleSubmit({name, contactList}, meta) {
        meta.props.save({name, contactList, meta});
    },
    enableReinitialize: true
};

const enhance = compose(
    connect(mapState, mapDispatch),
    withFormik(formikOption)
);
export default enhance(你的顶级组件);
`

其中validators专门另开一个文件:

`
import * as yup from 'yup';

export const name = yup
    .string()
    .max(40, '商品中心名称最多 40 个字符')
    .required('商品中心名称必填');

const contactName = yup
    .string()
    .matches(/^(?!.*;).*$/, '联系人名中不得带有“;”号')
    .required('联系人必填');

const email = yup
    .string()
    .email('必须输入正确的 Email 格式')
    .required('Email 地址必填');

export const contactList = yup.array().of(
    yup.object().shape({name: contactName, email})
);
`

有必要提一下注入Yup的好处:虽然formik已经极大的方便了表单的操作,但是我们还是可以看到在做表单验证的时候,写的代码还是比较繁琐,这个时候就需要yup出场了,yup的专长就是做规则校验。并且聪明的formik作者在设计formik的时候就让其很好的支持yup了。

可以看到使用Yup后,不管是从代码量,还是可读性来看都提交了很多,尤其是在表单非常复杂时,这之间的优化会非常的明显。Yup的操作非常的语义化,所以学习成本非常的低,你只需要知道它的api即可。

好了,让我们继续回头看看formikOption——

mapPropsToValues:也有initialValues。相应于表单初始字段值。Formik将使用这些值来生成例如props.values这样的方法组件。

即使你的表单默认情况下为空,你也必须使用初始值来初始化所有字段;否则,React会抛出异步,说你已经把一个输入字段从未控制(uncontrolled)状态改变成了可控制(controlled)状态。
initialValues并不适用于高阶组件(higher-order component);在高阶组件情况下,你需要mapPropsToValues。

enableReinitialize:默认值为false。此属性用于在initialValues变化时控制是否重置表单。

validate(values):其实本来上面yup的validationSchema在本Sample够用了,我主要说一些常见的特殊情况,比如一个互斥radio,选项A时需要inputA为非空,选项B时需要inputB为非空。
你这时候就需要写函数:

`
validate({radio, inputA, inputB}) {
    const errors = {};
    if (radio === 'A' && inputA === '') {
        errors.inputA = 'inputA不能为空';
    }
    if (radio === 'B' && inputB === '') {
        errors.inputB = 'inputB不能为空';
    }
    return errors;
}
`

其他的api,附录里面链接2讲的比较全哈~

实现

2个组件:

`
<Form>
    <FieldMcName name="name" />
    <FieldContact name="contactList" />
    <SubmitButtons />
</Form>
`

name必须和selectors对应,值需要正确初始化。

然后就是withFastField和withFieldArray登场了,这里是我们用formik的FastField和FieldArray进行的封装

`
const withFastField = Control => props => (
    <FastField {...props}>
        {fieldProps => <Control {...props} {...fieldProps} />}
    </FastField>
);
const withFieldArray = Control => props => (
    <FieldArray {...props}>
        {fieldProps => <Control {...props} {...fieldProps} />}
    </FieldArray>
);
`

随后我们就用这俩来包裹所开发的组件,组件里面props默认就有field和form

`
const FieldMcName = ({field, form}) => {
    return (
       .... 其它代码
       <Input
            name={field.name}
            value={field.value}
            onChange={field.onChange}
            onBlur={field.onBlur}
            placeholder="40 个字符以内"
        />
    );
};
export default withFastField(FieldMcName);

export default withFastArray(FieldContact); // 不再赘述
`

你看到这里可能会问:field是干啥的,form是干啥的?
field提供了一系列的方法onChange onBlur,一般情况下你只要像上面那个例子一样无脑用,就可以等同于把value记录在state里面(只不过这个state你没有维护而已!)

form.values就是这个form的state,field.value是这个组件的值,form.values是整个form的state值,有时候你可能用withFastField分拆了子组件,子组件有时候要看别的子组件的状态来做不同的展现,你就要用form.values。

form还有一些方法,比如antd的Input onChange传参数是e和field.onChange参数一致,但ant的Select Switch onChange传的是value/checked等,那这时候你Switch里面的onChange={field.onChange}就不对了,咋整呢?我们有:

`
const setFieldValue = form.setFieldValue;
const name = field.name;
const handleChange = useCallback(checked => setFieldValue(name, checked), [setFieldValue, name]);
`

formik还有一个api这里要用:formik.handleReset。把state里面的值重置为初始值。你看上面ue图,有一个取消按钮,如果点了的话就要调一下,不然你input输入了值,点击取消后,下次变为编辑态时,会看到上一次你自己输入的东西(和库里面的值不一致)。
handleSubmit很好理解,最上面的formikOption就有。

最后是提交按钮这个组件(formikConnect过):

`
export default connect(({handleCancel, formik: {errors, isSubmitting, handleSubmit, validateForm}}) => {
    useEffect(() => validateForm(), [validateForm]);
    const disabled = useMemo(() => isNotEmpty(errors), [errors]);
    return (
        <Fragment>
             <Button skin="important" disabled={disabled} onClick={handleSubmit} loading={isSubmitting}>
                确定
            </Button>
            <Button onClick={handleCancel} style={{marginLeft: '10px'}}>取消</Button>
        </Fragment>
    );
});
`

很贴心是吧,提交事件,disabled(校验不过的话),正在提交等状态都帮你想到了。就传一个取消事件进去就行了。

总结:子组件里面只需要formikConnect一下,就能有formik对象在props里面,可以用formik api。
子组件如果用FastField或者FieldArray包裹,就能在props里面有form和field对象,可以用field和form的api。
实在不行,你自己看文档,或者console打印一下,就ok啦~

附录

链接1
链接2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant