这是3.0Antd里Form的源代码
import { Form, Icon, Input, Button } from 'antd';
import React from 'react'
function hasErrors(fieldsError) {
return Object.keys(fieldsError).some(field => fieldsError[field]);
}
class HorizontalLoginForm extends React.Component {
componentDidMount() {
// To disable submit button at the beginning.
this.props.form.validateFields();
}
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
// Only show error after a field is touched.
const usernameError = isFieldTouched('username') && getFieldError('username');
const passwordError = isFieldTouched('password') && getFieldError('password');
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
<Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</Form.Item>
<Form.Item validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
Log in
</Button>
</Form.Item>
</Form>
);
}
}
const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);
export default WrappedHorizontalLoginForm;
我们来尝试自行封装一个Form表单组件
先简单写一个MFrom表单包含文本密码按钮,用一个高阶组件MFormCreate 封装一层
import React, { Component } from 'react'
const MFormCreate = Comp => {
return class extends Component{
constructor(props){
super(props);
this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
}
render(){
return (
<div>
<Comp {...this.props}></Comp>
</div>
)
}
}
}
class MFrom extends Component {
handleSubmit = () => {
alert('提交')
}
render() {
return (
<div>
<input type="text"/>
<input type="password" />
<input type="button" value="登录" onClick={this.handleSubmit} />
</div>
)
}
}
export default MFormCreate(MFrom);
此时我们要添加对表单项进行校验的功能
参考源代码里用getFieldDecorator高阶组件传值,包括表单项名称,校验规则,以及一个input标签。
- 改写MFrom渲染的dom结构
- 在MFrom的render方法中用this.props.form获取该函数
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
{
getFieldDecorator('username',{
rules:[
{
required: true,
message: '用户名是必填项'
}
]
})(<input type="text"/>)
}
{
getFieldDecorator('password',{
rules:[
{
required: true,
message: '密码是必填项'
}
]
})(<input type="password" />)
}
<input type="button" value="登录" onClick={this.handleSubmit} />
</div>
)
}
- 在MFormCreate组件中返回的Comp组件标签内添加form属性
<Comp {...this.props} form={this.form()}></Comp>
- 创建form方法,并且定义getFieldDecorator()
form = () => {return {getFieldDecorator: this.getFieldDecorator,}}
- 创建getFieldDecorator方法来实现对表单项变成控件
- 将rules配进options
- 将传进来的组件设置属性
- 当输入框更改时,用this.setState将最新的值保存
getFieldDecorator = (filedName,option) => {
//设置字段选项配置 存储校验错误的配置
this.options[filedName] = option;
// console.log(this.options);
return (InputComp) => {
// 更加api的方式
return <div>
{
React.cloneElement(InputComp,{
name:filedName, //控件的name
value: this.state[filedName] || '', //控件值
onChange: this.hanleChange
})
}
{
this.state[filedName+'Message'] && (
<p style={{color:'red'}}>{this.state[filedName + 'Message']}</p>
)
}
</div>
}
}
- 处理表单项输入事件hanleChange
- 获取输入框值并且setState更新值
- 设置完值后校验各个字段规则
hanleChange = (e) => {
const { name, value } = e.target;
//setState异步 可能先校验再设置值
this.setState({
[name]:value
},()=>{
//设置完值之后,再校验各个字段
this.validateField(name);
})
}
- 表单项的校验validateField
- 将表单项规则存储到定义的options
- 获取规则读取(这里是必填项校验 required)
- 根据校验结果设置state,并且返回值(让后面多次判断需求)
- 并且在第五步里添加一个p标签来返回校验不成功的信息
validateField = (filedName) => {
const { rules } = this.options[filedName];
const ret = rules.some(rule=>{
if(rule.required){
//输入框中为空,要出现错误的信息展示
if(!this.state[filedName]){
this.setState({
[filedName+'Message']:rule.message,
})
return true;//校验失败 返回true
}
}
})
//如果校验成功
if(!ret){
this.setState({
[filedName+"Message"]:'',
})
}
return !ret; // 校验成功返回 false
}
这时候已经可以实时获取校验结果了
- 提交校验handleSubmit
- 改写MFrom组件里的handleSubmit方法
handleSubmit = () => {
// 校验各个字段的必填项是否正确
this.props.form.validateFields((isValid)=>{
if(isValid){
alert('校验成功');
}else{
alert('校验失败,请检查规则');
}
})
}
- 在MFormCreate 函数中对应的form方法里添加对应字段
- 创建对应的方法validateFields
-
- 获取校验字段options里
-
- 将校验字段放到validateField里去校验
-
- 根据返回值来给出提示消息
validateFields = (cb) => {
const rets = [];
Object.keys(this.options).map(fieldName=>{
rets.push(this.validateField(fieldName))
})
const ret = rets.every(v=>v===true)
cb(ret);
}
截至到目前为止的完整代码
import React, { Component } from 'react'
//高阶组件
const MFormCreate = Comp => {
return class extends Component{
constructor(props){
super(props);
this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
}
//处理表单项输入事件
hanleChange = (e) => {
const { name, value } = e.target;
// console.log(`name:${name} value:${value}`);
//setState异步 可能先校验再设置值
this.setState({
[name]:value
},()=>{
//设置完值之后,再校验各个字段
this.validateField(name);
})
}
//表单项的校验
validateField = (filedName) => {
const { rules } = this.options[filedName];
const ret = rules.some(rule=>{
if(rule.required){
//输入框中为空,要出现错误的信息展示
if(!this.state[filedName]){
this.setState({
[filedName+'Message']:rule.message,
})
return true;//校验失败 返回true
}
}
})
//如果校验成功
if(!ret){
this.setState({
[filedName+"Message"]:'',
})
}
// console.log(this.state);
return !ret; // 校验成功返回 false
}
//按钮校验
validateFields = (cb) => {
console.log(Object.keys(this.options));
const rets = [];
Object.keys(this.options).map(fieldName=>{
rets.push(this.validateField(fieldName))
})
const ret = rets.every(v=>v===true)
cb(ret);
}
//1. 将rules配进options
// 2. 将传进来的组件设置属性
// 3. 当输入框更改时,用this.setState将最新的值保存
getFieldDecorator = (filedName,option) => {
//设置字段选项配置 存储校验错误的配置
this.options[filedName] = option;
// console.log(this.options);
return (InputComp) => {
// 更加api的方式
return <div>
{
React.cloneElement(InputComp,{
name:filedName, //控件的name
value: this.state[filedName] || '', //控件值
onChange: this.hanleChange
})
}
{
this.state[filedName+'Message'] && (
<p style={{color:'red'}}>{this.state[filedName + 'Message']}</p>
)
}
</div>
}
}
form = () => {
return {
getFieldDecorator: this.getFieldDecorator,
validateFields: this.validateFields,
}
}
render(){
return (
<div>
<Comp {...this.props} form={this.form()}></Comp>
</div>
)
}
}
}
class MFrom extends Component {
handleSubmit = () => {
// 校验各个字段的必填项是否正确
this.props.form.validateFields((isValid)=>{
if(isValid){
alert('校验成功');
}else{
alert('校验失败,请检查规则');
}
})
}
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
{
getFieldDecorator('username',{
rules:[
{
required: true,
message: '用户名是必填项'
}
]
})(<input type="text"/>)
}
{
getFieldDecorator('password',{
rules:[
{
required: true,
message: '密码是必填项'
}
]
})(<input type="password" />)
}
<input type="button" value="登录" onClick={this.handleSubmit} />
</div>
)
}
}
export default MFormCreate(MFrom);
FormItem和Input封装
FormItem
查看源代码的FormItem标签,有validateStatus和help属性来判断是否提示错误信息。所以是把错误信息的提示放到FormItem中来渲染。
- 根据源代码改写MFrom组件
render() {
const { getFieldDecorator, isFieldTouched, getFieldError } = this.props.form;
const usernameError = isFieldTouched('username') && getFieldError('username');
const passwordError = isFieldTouched('password') && getFieldError('password');
return (
<div>
<FormItem validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
{
getFieldDecorator('username', {
rules: [
{
required: true,
message: '用户名是必填项'
}
]
})(<input type="text" />)
}
</FormItem>
<FormItem validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
{
getFieldDecorator('password', {
rules: [
{
required: true,
message: '密码是必填项'
}
]
})(<input type="password" />)
}
</FormItem>
<input type="button" value="登录" onClick={this.handleSubmit} />
</div>
)
}
- 写FormItem子组件(把高阶组件中展示错误信息的代码去掉放在这里控制)
class FormItem extends Component {
render() {
return (
<div>
{this.props.children}
{/* 错误信息展示 */}
{
this.props.validateStatus && (
<p style={{ color: 'red' }}>{this.props.help}</p>
)
}
</div>
)
}
}
- MFormCreate组件中的form方法添加对应的字段
- 判断字段是否被点击过的方法isFieldTouched
- 这里要先在表单项控件getFieldDecorator中添加一个onFocus事件 当控件被点击的时候给state里添加
[fieldName+'Focus']:true
- 判断
[fieldName+'Focus']
是否为true来判断是否有被点击过
- 这里要先在表单项控件getFieldDecorator中添加一个onFocus事件 当控件被点击的时候给state里添加
- 获取错误信息getFieldError。获取state里的
[fieldName+'Message']
Input
- 根据源代码改写MForm组件的render方法
- 创建Input子组件
所有的完整代码
import React, { Component } from 'react'
import {Icon} from 'antd'
//高阶组件
const MFormCreate = Comp => {
return class extends Component {
constructor(props) {
super(props);
this.options = {}; //各个字段的选项值 它的变化不会影响组件渲染
this.state = {}; // 各个字段的值 它的变化 要出发校验的函数 对数据进行渲染
}
//处理表单项输入事件
hanleChange = (e) => {
const { name, value } = e.target;
// console.log(`name:${name} value:${value}`);
//setState异步 可能先校验再设置值
this.setState({
[name]: value
}, () => {
//设置完值之后,再校验各个字段
this.validateField(name);
})
}
//表单项的校验
validateField = (fieldName) => {
const { rules } = this.options[fieldName];
const ret = rules.some(rule => {
if (rule.required) {
//输入框中为空,要出现错误的信息展示
if (!this.state[fieldName]) {
this.setState({
[fieldName + 'Message']: rule.message,
})
return true;//校验失败 返回true
}
}
})
//如果校验成功
if (!ret) {
this.setState({
[fieldName + "Message"]: '',
})
}
// console.log(this.state);
return !ret; // 校验成功返回 false
}
//按钮校验
validateFields = (cb) => {
console.log(Object.keys(this.options));
const rets = [];
Object.keys(this.options).map(fieldName => {
rets.push(this.validateField(fieldName))
})
const ret = rets.every(v => v === true)
cb(ret);
}
handleFocus = (e) => {
const fieldName = e.target.name;
this.setState({
[fieldName + 'Focus']: true
})
}
//1. 将rules配进options
// 2. 将传进来的组件设置属性
// 3. 当输入框更改时,用this.setState将最新的值保存
getFieldDecorator = (fieldName, option) => {
//设置字段选项配置 存储校验错误的配置
this.options[fieldName] = option;
// console.log(this.options);
return (InputComp) => {
// 更加api的方式
return <div>
{
React.cloneElement(InputComp, {
name: fieldName, //控件的name
value: this.state[fieldName] || '', //控件值
onChange: this.hanleChange,
onFocus: this.handleFocus
})
}
</div>
}
}
//判断控件是否被点击过
isFieldTouched = (fieldName) => {
return !!this.state[fieldName + 'Focus']
}
//获取控件的错误的提示信息
getFieldError = (fieldName) => {
return this.state[fieldName + 'Message']
}
form = () => {
return {
getFieldDecorator: this.getFieldDecorator,
validateFields: this.validateFields,
isFieldTouched: this.isFieldTouched,
getFieldError: this.getFieldError,
}
}
render() {
return (
<div>
<Comp {...this.props} form={this.form()}></Comp>
</div>
)
}
}
}
class FormItem extends Component {
render() {
return (
<div>
{this.props.children}
{/* 错误信息展示 */}
{
this.props.validateStatus && (
<p style={{ color: 'red' }}>{this.props.help}</p>
)
}
</div>
)
}
}
class Input extends Component {
render() {
return (
<div>
{this.props.prefix}
<input type="text" {...this.props} />
</div>
)
}
}
class MFrom extends Component {
handleSubmit = () => {
// 校验各个字段的必填项是否正确
this.props.form.validateFields((isValid) => {
if (isValid) {
alert('校验成功');
} else {
alert('校验失败,请检查规则');
}
})
}
render() {
const { getFieldDecorator, isFieldTouched, getFieldError } = this.props.form;
const usernameError = isFieldTouched('username') && getFieldError('username');
const passwordError = isFieldTouched('password') && getFieldError('password');
return (
<div>
<FormItem validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
{
getFieldDecorator('username', {
rules: [
{
required: true,
message: '用户名是必填项'
}
]
})(<Input
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
type="text"
/>)
}
</FormItem>
<FormItem validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
{
getFieldDecorator('password', {
rules: [
{
required: true,
message: '密码是必填项'
}
]
})(<Input
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>)
}
</FormItem>
<input type="button" value="登录" onClick={this.handleSubmit} />
</div>
)
}
}
export default MFormCreate(MFrom);
思想小结: 状态提升。将逻辑操作部分尽量放到高阶组件中去完成,而像Input等子组件只进行展示。