Skip to content

ReactNative表单验证探究(一)

chen edited this page Jul 22, 2018 · 3 revisions

在任何系统的开发工作中,表单的验证都是一项必不可少的工作,在ReactNative的开发过程中也不过如此。

现状

​ 由于ReactNative原生并没有提供表单验证功能,因此只能求助于第三方的一些插件,目前比较常用的ReactNative表单验证插件有以下几种:

  1. react-native-gifted-form

    react-native-gifted-form是一款非常棒的ReactNative表单验证插件,页面效果非常酷炫,上手也不是很难;但是该项目作者已经很长时间没有维护,并且表单控件只能使用该插件提供的一些控件。

  2. tcomb-form-native

    tcomb-form-native是一款非常容易上手的ReactNative表单验证插件,star也达到了将近3k;同样它的缺点也是表单控件只能使用该插件提供的一些控件,并且表单控件的效果与我们的需求差异很大。

  3. rc-form

    rc-form是antd所推荐的一款表单验证插件,它支持ReactNative,可以使用自己封装的表单控件;但是看完官方给出的demo之后,完全懵逼,瞬间感觉遇到了一块硬骨头。

取舍

​ 以上三种表单验证插件都是非常棒的,各有优缺点,关于要使用哪一种完全取决于自己的业务需求,由于目前的业务需求,我选择了rc-form

rc-from for ReactNative

先来看下官方给出的demo,代码如下:

import React from 'react'
import PropTypes from 'prop-types'
import {
  StyleSheet,
  Button,
  Dimensions,
  TextInput,
  Text,
  View,
  Alert,
} from 'react-native'

import { createForm } from 'rc-form'

const { width } = Dimensions.get('window')
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    padding: 50,
    justifyContent: 'center',
  },
  inputView: {
    width: width - 100,
    paddingLeft: 10,
  },
  input: {
    height: 42,
    fontSize: 16,
  },
  errorinfo: {
    marginTop: 10,
  },
  errorinfoText: {
    color: 'red',
  },
})

class FromItem extends React.PureComponent {
  getError = error => {
    if (error) {
      return error.map(info => {
        return (
          <Text style={styles.errorinfoText} key={info}>
            {info}
          </Text>
        )
      })
    }
  }
  render() {
    const { label, onChange, value, error } = this.props
    return (
      <View style={styles.inputView}>
        <TextInput
          style={styles.input}
          value={value || ''}
          label={`${label}:`}
          duration={150}
          onChangeText={onChange}
          highlightColor="#40a9ff"
          underlineColorAndroid="#40a9ff"
        />
        <View style={styles.errorinfo}>{this.getError(error)}</View>
      </View>
    )
  }
}

class App extends React.Component {
  static propTypes = {
    form: PropTypes.object.isRequired,
  }

  checkUserNameOne = (value, callback) => {
    setTimeout(() => {
      if (value === '15188888888') {
        callback('手机号已经被注册')
      } else {
        callback()
      }
    }, 2000)
  }
  submit = () => {
    this.props.form.validateFields((error) => {
      if (error) return
      Alert('通过了所有验证') // eslint-disable-line new-cap
    })
  }
  render() {
    const { getFieldDecorator, getFieldError } = this.props.form
    return (
      <View style={styles.container}>
        <Text>简单的手机号验证</Text>
        {getFieldDecorator('username', {
          validateFirst: true,
          rules: [
            { required: true, message: '请输入手机号!' },
            {
              pattern: /^1\d{10}$/,
              message: '请输入正确的手机号!',
            },
            {
              validator: (rule, value, callback) => {
                this.checkUserNameOne(value, callback);
              },
              message: '手机号已经被注册!',
            },
          ],
        })(
          <FromItem
            autoFocus
            placeholder="手机号"
            error={getFieldError('username')}
          />
        )}
        <Button color="#40a9ff" onPress={this.submit} title="登陆" />
      </View>
    )
  }
}

export default createForm()(App)

​ 看完这段代码,相信大部分人跟我的感觉是一样的(不就是一个表单验证吗,怎么使用起来这么复杂,让我瞬间怀念起使用Vue的那段日子)。之前做过Vue相关的开发,elementUI和iView中的表单组件使用起来是多么的简洁易懂,怎么到了ReactNative中就变的这么的复杂难懂。先不吐槽了~,来梳理一下这个demo中的思路吧,毕竟思路清晰以后我们还是可以将其再次封装的(毕竟再好用的插件也都是人家进行多次封装之后供我们使用的,这也是一次技术提升的好机会)。

​ 我先来讲一下大致的思路,在代码的最后一句,导出了createForm()(App),createForm是一个React高阶函数,它接收一个React组件(App),在函数内部对该组件进行加工改造,最后返回加工改造之后的新组件。在createForm函数中其将form对象传递给App组件,因此通过this.props.form就可以获取到这个form对象。

const { getFieldDecorator, getFieldError } = this.props.form
....
{getFieldDecorator('username', {
	validateFirst: true,
	rules: [
		{ required: true, message: '请输入手机号!' },
		{
			pattern: /^1\d{10}$/,
			message: '请输入正确的手机号!',
         },
		{
        	validator: (rule, value, callback) => {
		    	this.checkUserNameOne(value, callback);
			},
			message: '手机号已经被注册!',
         },
	],
})(
	<FromItem
		autoFocus
		placeholder="手机号"
		error={getFieldError('username')}
	/>
)}
...

getFieldDecorator()函数

form提供了getFieldDecorator函数。其函数定义形式为

getFieldDecorator(fieldName, fieldOption) => (Component) => ComponentWithExtraProps

getFieldDecorator函数接收两个参数,分别是

  • fieldName:字段名称
  • fieldOption:字段操作对象

源码

/** 
 *  {getFieldDecorator(name,fieldOption)(<FormItem {...props}/>)}将表单项包装为高阶组件后返回
 *  实现功能同getFieldProps方法,内部也调用getFieldProps方法
 *  与getFieldProps方法不同的是,被封装表单项的props作为this.fieldMeta[name]的originalProps属性
 *  originalProps属性的主要目的存储被封装表单项的onChange事件,fieldOption下无同类事件时,执行该事件
 *  不推荐将value、defaultValue作为表单项组件如FormItem的props属性
 */
getFieldDecorator: function getFieldDecorator(name, fieldOption) {
	var _this = this;
	// 获取需要传递给被修饰元素的属性。包括onChange,value等
    // 同时在该props中设定用于收集元素值得监听事件(onChange),以便后续做双向数据。
	var props = this.getFieldProps(name, fieldOption);
     return function (fieldElem) {
     	  // 此处fieldStore存储字段数据信息以及元数据信息。
          // 数据信息包括value,errors,dirty等
          // 元数据信息包括initValue,defaultValue,校验规则等。
          var fieldMeta = _this.getFieldMeta(name);
          var originalProps = fieldElem.props;
          ...
          fieldMeta.originalProps = originalProps;
          fieldMeta.ref = fieldElem.ref;
          return _react2["default"].cloneElement(fieldElem, (0, _extends3["default"])({}, props, _this.getFieldValuePropValue(fieldMeta)));
      };
};

其返回值是一个React高阶函数,接收参数是FormItem自定义组件,在该高阶函数中将onChange以及value传递给FormItem,然后在FormItem组件中

...
const { label, onChange, value, error } = this.props
return (
	<View style={styles.inputView}>
		<TextInput
			style={styles.input}
			value={value || ''}
			label={`${label}:`}
			duration={150}
			onChangeText={onChange}
			highlightColor="#40a9ff"
			underlineColorAndroid="#40a9ff"
		/>
		<View style={styles.errorinfo}>{this.getError(error)}</View>
	</View>
)
...

获取到onChange以及value后,将其绑定在TextInput组件的onChangeText和value上。(TextInput可以替换为其他的组件,但是替换的组件一定要有value和onChange属性)

getFieldError()函数

form同样还提供了getFieldError函数。其函数定义形式为

getFieldError(fieldName) => errors数组

getFieldError函数接收一个参数:

  • fieldName:字段名称(要与getFieldDecorator函数的第一个参数fieldName保持一致)

源码

// 获取this.fields[name]["errors"]错误数据,并剔除没有error.message的错误数据
getFieldError: function getFieldError(name) {
	return (0, _utils.getErrorStrs)(this.getFieldMember(name, 'errors'));
},
// 获取this.fields[name][member]属性数据
getFieldMember: function getFieldMember(name, member) {
	var field = this.getField(name);
	return field && field[member];
},

其返回值是一个数组,数组中存储的是错误信息,拿到错误信息之后,将其传递给FormItem的error属性

...
<FromItem
	...
	error={getFieldError('username')}
/>
...
// 在FromItem.js中
...
const { label, onChange, value, error } = this.props
return (
	<View style={styles.inputView}>
		...
		<View style={styles.errorinfo}>{this.getError(error)}</View>
	</View>
)
...
// 返回error信息组件函数
getError = error => {
    if (error) {
      return error.map(info => {
        return (
          <Text style={styles.errorinfoText} key={info}>
            {info}
          </Text>
        )
      })
    }
  }

​ 在FromItem组件中,获取到error数组对象之后,调用error信息展示函数,该函数返回错误信息展示组件,从而将错误信息展示给用户。

结束语

​ 以上就是在ReactNative中使用rc-form的基本思路,想必大家看了demo代码之后一定在想一个问题,如果页面中需要表单验证的组件非常多,那么render函数中的代码量也是非常庞大和冗余的,怎么将这些冗余的代码给抽离出来,怎么给render函数瘦身,这些问题我会在ReactNative表单验证(二)中进行讲解,敬请期待~