ReactNative表单验证探究(二)

983 阅读4分钟

本文由我们组内的陈嘉辉总结

​ 在ReactNative表单验证探究(一)一文中我简单的分析了较流行的ReactNative表单验证插件的各自的优缺点,并着重的讲解了我所选择的rc-form的使用以及关键函数的源码分析,最后我们发现rc-form作者给出的demo中,表单验证使用起来比较复杂难懂,并且render函数非常的复杂臃肿,在本文中我将会向大家展示如何给render函数进行瘦身,以及对设计模式中的装饰者模式表述一下我自己的理解。

表单组件的封装

​ 要想对render函数进行瘦身,我们首先要从自定义表单项组件入手。上一篇我们讲到getFieldDecorator函数接收fieldName字段名称和fieldOption字段操作对象两个参数,并返回一个React高阶函数(该高阶函数的参数就是自定义表单组件),在该高阶函数中将onChange以及value传递给自定义表单项组件并返回一个加工后的组件对象。因此,我们可以将getFieldDecorator函数放到自定义表单组件中去,代码如下所示:

// FormItem.js

import BaseFormItem from './base-form-item' // 表单项组件基类

class FromItem extends BaseFormItem {
  render() {
    const { label, onChange, value, prop, rules, form } = this.props;
    const { getFieldDecorator, getFieldError } = form;
    return (
      <View style={styles.inputView}>
          {getFieldDecorator(prop, {rules})(
              <TextInput
              style={styles.input}
              value={value || ''}
              label={`${label}:`}
              duration={150}
              onChangeText={onChange}
              highlightColor="#40a9ff"
              underlineColorAndroid="#40a9ff"
            />
            <View style={styles.errorinfo}>{this.getError(getFieldError(prop))}</View>
          )}
      </View>
    );
  }
}

​ 我们可以看到,上面的代码将getFieldDecorator放到了FormItem组件中去,在页面组件中将form对象,prop和rules传递给FormItem组件,然后在FormItem组件中从页面组件传递过来的form对象中取出getFieldDecoratorgetFieldError函数,getFieldDecorator函数接收的两个参数fieldName(页面组件传递过来的prop)、fieldOption(页面组件传递过来的rules)。

/**
 * 表单项基类
 */
class BaseFormItem extends Component {
  getError = (error) => {
    if (error) {
      return error.map(info => <Text style={{color: 'red'}} key={info}>{info}</Text>)
    }
    return null
  }
}

​ 每一个表单子项都会有显示错误信息的方法,为了避免写重复代码,我们将显示错误信息的方法抽离出来,并创建一个基类,将该方法放到基类中,让所有表单项来继承这个基类。

class App extends React.Component {
    constructor(props) {
        super(props)

        this.state = {
          rules: { // 定义校验规则
            username: [
                  { required: true, message: '请输入手机号!' },
                {
                  pattern: /^1\d{10}$/,
                  message: '请输入正确的手机号!',
                },
                {
                  validator: (rule, value, callback) => {
                    this.checkUserNameOne(value, callback);
                  },
                  message: '手机号已经被注册!',
                },
            ],
          },
        }
    }
    ...
    render() {
        const { form } = this.props
        return (
            <View>
              <FromItem
                  form={form}
                  prop="username"
                  rules={this.state.rules.username}
                autoFocus
                placeholder="手机号"
              />
              <Button color="#40a9ff" onPress={this.submit} title="登陆" />
              </View>
        )
    }
}

​ 经过改造之后,页面的render函数代码是不是瞬间减少了。但是这个时候我们要考虑另外一个问题,页面中render函数确实被瘦身了,但是表单组件中的render函数却又开始变的臃肿了。如果我们需要来封装一个选择器表单组件,那FormItem中的那段臃肿的代码是不是还要再被重写一遍呢;如果我们后续开发的过程中rc-form的实现形式改变了,或者是换了另外一个表单校验插件,那么我们之前封装的表单组件就全部要进行修改;我们封装的表单组件只能用于表单验证,如果想在非表单验证页面上使用的话也是不可以的。综上所述组件与表单验证是强耦合的,我们能不能将组件表单验证这两个东西给分开呢?试想一下,组件如果不披表单验证这件外衣它就是一个单纯的组件,如果披了这件外衣,它就是带有表单验证功能的组件,表单验证实现的改变不会影响到组件。怎么才能达到这种理想状态呢?

装饰者模式

​ 绕了一大圈子,终于把本文的重点装饰者模式引了出来。

什么是装饰者模式?

​ 装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。通常情况下要给对象扩展功能使用继承的方式,但是继承的方式并不灵活,还会带来许多问题:一方面会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变。

​ 我们将表单项中与rc-form相关的代码抽离出来,封装为一个装饰函数,该函数接收一个组件对象,返回一个经过加工的组件对象,我们可以将表单插件的相关代码放到被加工的组件中,这样在表单插件改变之后只需要来修改这个装饰函数就可以了。

/**
 * 表单项装饰函数(详情可参考React高阶组件)
 * @param {*} WrappedComponent 传入React组件对象
 * @returns 经过加工后新的React组件
 */
const formItemDecorator = function formItemDecorator(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      const { form, prop, rules } = this.props
      const { getFieldDecorator } = form
      this.fieldDecorator = getFieldDecorator(prop, {rules: [...rules]})
    }

    render() {
      const {form, prop} = this.props
      const {getFieldError} = form
      return (
        <View>
          {this.fieldDecorator(<WrappedComponent {...this.props} error={getFieldError(prop)} />)}
        </View>
      )
    }
  }
}


//InputItem.js中使用
export default formItemDecorator(InputItem)