Open tiodot opened 7 years ago
在 #17 仿造async-validator实现了一个简易版的验证器。为了实现 #16 中提及的jQuery Form Validator类似的验证方式,一个验证器似乎是杯水车薪,路漫漫该如何走呢?
在antd提供的form表单组件,其将组件和验证规则关联需要通过一个getFieldDecorator来完成,其形式是:
getFieldDecorator
import { Form, Icon, Input, Button, Checkbox } from 'antd'; const FormItem = Form.Item; class NormalLoginForm extends React.Component { handleSubmit = (e) => { e.preventDefault(); this.props.form.validateFields((err, values) => { if (!err) { console.log('Received values of form: ', values); } }); } render() { const { getFieldDecorator } = this.props.form; return ( <Form onSubmit={this.handleSubmit} className="login-form"> <FormItem> {getFieldDecorator('userName', { rules: [{ required: true, message: 'Please input your username!' }], })( <Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="Username" /> )} </FormItem> <FormItem> {getFieldDecorator('password', { rules: [{ required: true, message: 'Please input your Password!' }], })( <Input prefix={<Icon type="lock" style={{ fontSize: 13 }} />} type="password" placeholder="Password" /> )} </FormItem> <Button type="primary" htmlType="submit" className="login-form-button"> Log in </Button> </FormItem> </Form> ); } } const WrappedNormalLoginForm = Form.create()(NormalLoginForm); ReactDOM.render(<WrappedNormalLoginForm />, mountNode);
为了使用getFieldDecorator,最后还得将组件用Form.create()包一层,Form.create()会自动给被包裹的组件注入一个form到组件的props中。看起来就是那么不直观。
Form.create()
form
props
还有一种方式就是Lesha-spr/react-validation使用的提供支持验证的组件,附其一个github上的demo:
import React, {Component, PropTypes} from 'react'; import Validation from 'react-validation'; import validator from 'validator'; Object.assign(Validation.rules, { required: { rule: value => { return value.toString().trim(); }, hint: value => { return <span className='form-error is-visible'>Required</span> } }, email: { rule: value => { return validator.isEmail(value); }, hint: value => { return <span className='form-error is-visible'>{value} isnt an Email.</span> } } }); export default class Registration extends Component { render() { return <Validation.components.Form> <h3>Registration</h3> <div> <label> Email* <Validation.components.Input value='email@email.com' name='email' validations={['required', 'email']}/> </label> </div> <div> <Validation.components.Button>Submit</Validation.components.Button> </div> </Validation.components.Form>; } }
虽然看起来和jquery的form validator很像了,但一个不足就是input,select之类的都需要使用其提供的组件,不方便扩展,或者说限制较多。
在ES5时代的React推出了Mixin机制来提高代码复用率,然而ES6时代被HOCs无情的取代,虽然未被官方承认,但redux,flux等用的都是这种思路,像上面提及的antd中form组件的Form.create就使用到了HOCs。HOCs(High Order Components:高阶组件)类似于高级函数。在深入理解 React 高阶组件中提到一种高级组件的用途Inheritance Inversion,简直就是为实现表单验证的最佳实践。先看其最简单的一个示例:
Mixin
Form.create
Inheritance Inversion
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { return super.render() } } }
可以看到,返回的 HOC 类(Enhancer)继承了 WrappedComponent,而不是 WrappedComponent 继承了 Enhancer。意味着Enhancer可以访问到 WrappedComponent中的state、props、组件生命周期方法和 render 方法。能否访问render方法,意味着就能访问其对应的虚拟DOM,虚拟DOM是啥,就是真实DOM的js的object表示。蓦然回首,答案已呼之欲出。
提供一个HOC类,需要完成以下几个功能:
const elementsTree = super.render(); // 组件的render方法返回就是虚拟DOM
data-validation
cloneElement
validate
完整代码:
import React, {cloneElement} from 'react'; import Schema from '../verification'; export default function formWrapper(WrappedComponent) { return class Enhancer extends WrappedComponent { constructor(props) { super(props); this.schema = new Schema(); this.store = {}; super.validate = this.validate.bind(this); // 给组件注入validate方法 } validate(callback) { const store = this.store; this.schema.validate(store, function (error) { console.log('result is: ', error); callback && callback(error, store); }) } onChangeWithValidation(name, oldOnChange) { const schema = this.schema; const store = this.store; return function (e) { let error; const value = e.target.value; store[name] = value; console.log('wrapper change callback'); schema.validate({[name]: value}, {keys: [name]}, function (err) { console.log('validate result: ', err); error = err; }); oldOnChange && oldOnChange.call(oldOnChange, e, error, name); } } parseRule(elementsTree) { // 递归解析节点,根据是否有data-validation来判断是否需要校验 const parseRule = this.parseRule.bind(this); if (!elementsTree.props) { // 文本节点 return elementsTree; } let children = React.Children.map(elementsTree.props.children, function (child) { return parseRule(child); }); let rules = elementsTree.props['data-validation']; let {name, onChange} = elementsTree.props; let schema = this.schema; let newProps = {}; if (name && rules) { schema.extend({ [name]: rules }); newProps.onChange = this.onChangeWithValidation(name, onChange); } const props = Object.assign({}, elementsTree.props, newProps); return cloneElement(elementsTree, props, children); } render() { const elementsTree = super.render(); return this.parseRule(elementsTree); } } }
HOC已完成,那如何使用?
import React, {Component} from 'react'; import './simple-form.less'; import FormWrapper from '../form/wrapper'; class SimpleForm extends Component { constructor(props) { super(props); this.state = { error: '' } } onChange(e, error, name) { console.log(e.target.value); console.log('error: ', JSON.stringify(error)); this.setState({error: error ? error[0].message : ''}); } render() { return ( <form> <input name="name" onChange={this.onChange.bind(this)} type="text" data-validation={[{required: true}]}/> <span style={{color: '#f00'}}>{this.state.error}</span> </form> ); } } export default FormWrapper(SimpleForm); // 使用HOC类装饰
代码以及示例地址:react-validator 最后看看效果:
深入理解 React 高阶组件
在 #17 仿造async-validator实现了一个简易版的验证器。为了实现 #16 中提及的jQuery Form Validator类似的验证方式,一个验证器似乎是杯水车薪,路漫漫该如何走呢?
寻常路
提供工具函数
在antd提供的form表单组件,其将组件和验证规则关联需要通过一个
getFieldDecorator
来完成,其形式是:为了使用
getFieldDecorator
,最后还得将组件用Form.create()
包一层,Form.create()
会自动给被包裹的组件注入一个form
到组件的props
中。看起来就是那么不直观。提供带验证组件
还有一种方式就是Lesha-spr/react-validation使用的提供支持验证的组件,附其一个github上的demo:
虽然看起来和jquery的form validator很像了,但一个不足就是input,select之类的都需要使用其提供的组件,不方便扩展,或者说限制较多。
进阶之路
在ES5时代的React推出了
Mixin
机制来提高代码复用率,然而ES6时代被HOCs无情的取代,虽然未被官方承认,但redux,flux等用的都是这种思路,像上面提及的antd中form组件的Form.create
就使用到了HOCs。HOCs(High Order Components:高阶组件)类似于高级函数。在深入理解 React 高阶组件中提到一种高级组件的用途Inheritance Inversion
,简直就是为实现表单验证的最佳实践。先看其最简单的一个示例:可以看到,返回的 HOC 类(Enhancer)继承了 WrappedComponent,而不是 WrappedComponent 继承了 Enhancer。意味着Enhancer可以访问到 WrappedComponent中的state、props、组件生命周期方法和 render 方法。能否访问render方法,意味着就能访问其对应的虚拟DOM,虚拟DOM是啥,就是真实DOM的js的object表示。蓦然回首,答案已呼之欲出。
提供一个HOC类,需要完成以下几个功能:
data-validation
属性cloneElement
将修改的属性扩展回虚拟DOM中validate
方法,用于验证表单数据完整代码:
HOC已完成,那如何使用?
代码以及示例地址:react-validator 最后看看效果:
参考
深入理解 React 高阶组件