本文由我们团队的陈嘉辉总结
在任何系统的开发工作中, 表单的验证都是一项必不可少的工作, 在 ReactNative 的开发过程中也不过如此.
现状
由于 ReactNative 原生并没有提供表单验证功能, 因此只能求助于第三方的一些插件, 目前比较常用的 ReactNative 表单验证插件有以下几种:
react-native-gifted-form
react-native-gifted-form 是一款非常棒的 ReactNative 表单验证插件, 页面效果非常酷炫, 上手也不是很难; 但是该项目作者已经很长时间没有维护, 并且表单控件只能使用该插件提供的一些控件.
tcomb-form-native
tcomb-form-native 是一款非常容易上手的 ReactNative 表单验证插件, star 也达到了将近 3k; 同样它的缺点也是表单控件只能使用该插件提供的一些控件, 并且表单控件的效果与我们的需求差异很大.
rc-form
rc-form 是 antd 所推荐的一款表单验证插件, 它支持 ReactNative, 可以使用自己封装的表单控件; 但是看完官方给出的 demo 之后, 完全懵逼, 瞬间感觉遇到了一块硬骨头.
取舍
以上三种表单验证插件都是非常棒的, 各有优缺点, 关于要使用哪一种完全取决于自己的业务需求, 由于目前的业务需求, 我选择了 rc-form.
rc-from for ReactNative
先来看下官方给出的 demo https://github.com/react-component/form/blob/master/examples/react-native/App.js , 代码如下:
- 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 表单验证 (二) 中进行讲解, 敬请期待~
来源: https://juejin.im/entry/5b5536056fb9a04f9a5cd77b