一个 React Form 组件的重构思路
最近对团队内部 React 组件库(ne-rc)中的 Form 组件进行了重构,记录一下思考的过程。 一些前置定义:
首先我们看一下,我们的对 Form 组件的需求是什么。
接着我们从重构前和重构后,看如何来解决这个问题。 Before获取当前变动表单的状态如何获取变动的子表单React 父子通信需要通过 prop 传递方法,对于 Form 下面的类似与 Input 之类的子表单的变化想要通知到父级,如果不借助第三方的事件传递方法,那么就只能通过由父级通过 props 向 Input 传递 那么问题来了,什么时候去传递这个方法呢? 不能在具体页面里面使用的时候再去每条表单里面注册这个方法,那每个用到表单组件的时候就都需要给子表单进行这样的事件绑定,这样太累了。 所以一开始,我选择通过直接递归的遍历 Form 下面的 children,只要发现这个 children 是我想要的表单类型,那么就重新克隆一个带有 /** * 获取 form 下面每一个表单对象,注入属性,并收集起来 * @param children * @returns {*} */ function getForms(children) { return React.Children.map(children,(el,i) => { if (!el) { return null } switch (el.type) { case Input: Forms.push(el) return React.cloneElement( el,{ key: i,formFieldChange,emptyInput } ) case Select: Forms.push(el) return React.cloneElement( el,formFieldChange } ) case CheckBox: Forms.push(el) return React.cloneElement( el,formFieldChange } ) default: if (el.props && el.props.children instanceof Array) { const children = getForms(el.props.children) return React.cloneElement( el,{ key: i,children } ) } else { return el } } }) } 这样,所有的特定子组件就都可以拿到被注册的方法。以 Input 为例,在 Input 的 收集变动表单的数据。前一步完成后,这一步就比较简单了,Input 在调用 校验表单是否填写完成前面我们收集了每一条变动表单的数据。但是要判断当前 Form 下面的表单是否填写完成,那么首先需要知道我们有多少个需要填写的表单,然后在 export default class Form extends React.Component { constructor(props) { super(props) this.Forms = [] this.formState = Object.assign({},{ isComplete: false,isValidate: false,errorMsg: '',data: {} },this.props.formState) } static propTypes = { onChange: PropTypes.func,onSubmit: PropTypes.func,formState: PropTypes.object } // 初始化一个类似这样的对象传递给 Form formState: { data: { realName: {},cityId: {},email: {},relativeName: {},relativePhone: {},companyName: {} } }, 这样就很粗暴的解决了这个问题,但是这中间存在很多问题。 因为限定了特定的组件类型(Input,Select,CheckBox),导致不利于扩展,如果在开发过程遇到其他类型的比如自定义的子表单,那么 Form 就没法对这个自定义子表单进行数据收集,解决起来比较麻烦。 所以就在考虑另一个种实现方式, Form 只去收集一个特定条件下的组件,只要这个组件满足了这个条件,并实现了对应的接口,那么 Form 就都可以去收集处理。这样也就大大挺高了适用性。 暴露对外提供整个表单状态的方法通过在外监听每次 Form 触发的 提交方法检验表单是否通过校验已经有了整个 Form 的数据对象,做校验并不是什么困难。通过校验的时候调用 对外触发 formSubmit 方法当表单通过校验的时候,对外触发 After前面是之前写的 Form 组件的一些思路,在实际使用中也基本能满足业务需求。 但是整个 Form 的可拓展性比较差,无法很好的接入其他自定义的组件。所以萌生了重写的想法。 对于重写的这个 Form,我的想法是:首先一定要方便使用,不需要一大堆的起始工作;其次就是可拓展性要强,除了自己已经提供的内在 Input,Select 等能够接入 Form 外,对于其他的业务中的特殊需求需要接入 Form 的时候,只要这个组件实现了特定的接口就可以了很方便的接入,而不需要大量的去修改组件内部的代码。 重构主要集中在上面需求 1 里面的内容,也就是:__获取当前变动表单的状态__ 获取当前表单的状态分解下来有一下几点:
获取当前变动表单的状态获取所有需要的子表单同样通过递归遍历 children 来获取需要收集的子表单,通过子表单的 type.name 命名规则是否符合我们的定义来决定是否要进行收集。 collectFormField = (children) => { const handleFieldChange = this.handleFieldChange // 简单粗暴,在 Form 更新的时候直接清空上一次保存的 formFields,全量更新, // 避免 formFields 内容或者数量发生变化时 this.formFields 数据不正确的问题 const FormFields = this.formFields = [] function getChildList(children) { return React.Children.map(children,i) => { // 只要 Name 以 _Field 开头,就认为是需要 From 管理的组件 if (!el || el === null) return null const reg = /^_Field/ const childName = el.type && el.type.name if (reg.test(childName)) { FormFields.push(el) return React.cloneElement(el,{ key: i,handleFieldChange }) } else { if (el.props && el.props.children) { const children = getChildList(el.props.children) return React.cloneElement(el,children }) } else { return el } } }) } 只要组件的 class name 以 _Field 开头,就把它收集起来,并传入 接入组件里面需要做的就是,在合适的时机调用 为什么一定要执迷不悟的使用遍历这种低效的方式去收集呢,其实都是为了组件上使用的方便。这样就不需要每次在引用的时候在对子表单做什么操作了。 初始化 Form state上一步拿到了所有的子表单,然后通过调用 子表单数量或类型发生变化时当 Form 下面子组件被添加或删除时,需要及时更新 Form Data 的结构。通过调用 子表单内部状态发生变化时在第一步收集子表单的时候就已经把 这样看起来整个流程就走通了,但实际上存在很多问题。 首先由于 所以我创建了一个临时变量 另一个问题是当 Form 发生变化的时候, constSTATUS={ Init:'Init',Normal:'Normal',FieldChange:'FieldChange',UpdateFormDataStructure:'UpdateFormDataStructure',Submit:'Submit' } 这样,只有在 Form 的 提交和对外暴露 Form 状态的方法和之前基本一致,这样整个对 Form 的重构就算完成了,具体项目中使用体验还不错 O(∩_∩)O Form 组件地址: https://github.com/NE-LOAN-FED/NE-Component/tree/master/src/Form 最后,如果看文章的你有什么更好的想法,请告诉我 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |