本文由我们组内的陈嘉辉总结
在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对象中取出getFieldDecorator和getFieldError函数,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)