如何實現Ant design表單組件封裝?
阿新 • • 發佈:2019-04-24
() type 變化 參數傳遞 document true 業務 提交 data-
目標:自己實現一個antd表單組件
先看下Ant Design官網上給出的表單組件用法:
1 import React, { Component } from ‘react‘ 2 import { Form, Icon, Input, Button } from ‘antd‘ 3 4 function hasErrors(fieldsError) { 5 return Object.keys(fieldsError).some(field => fieldsError[field]) 6 } 7 8 class HorizontalLoginForm extends React.Component {9 componentDidMount() { 10 // To disabled submit button at the beginning. 11 this.props.form.validateFields() 12 } 13 14 handleSubmit = e => { 15 e.preventDefault() 16 this.props.form.validateFields((err, values) => { 17 if (!err) { 18 console.log(‘Received values of form: ‘, values)19 } 20 }) 21 }; 22 23 render() { 24 const { 25 getFieldDecorator, 26 getFieldsError, 27 getFieldError, 28 isFieldTouched 29 } = this.props.form 30 31 // Only show error after a field is touched. 32 const userNameError = 33 isFieldTouched(‘userName‘) && getFieldError(‘userName‘)34 const passwordError = 35 isFieldTouched(‘password‘) && getFieldError(‘password‘) 36 return ( 37 <Form layout=‘inline‘ onSubmit={this.handleSubmit}> 38 <Form.Item 39 validateStatus={userNameError ? ‘error‘ : ‘‘} 40 help={userNameError || ‘‘} 41 > 42 {getFieldDecorator(‘userName‘, { 43 rules: [{ required: true, message: ‘Please input your username!‘ }] 44 })( 45 <Input 46 prefix={<Icon type=‘user‘ style={{ color: ‘rgba(0,0,0,.25)‘ }} />} 47 placeholder=‘Username‘ 48 /> 49 )} 50 </Form.Item> 51 <Form.Item 52 validateStatus={passwordError ? ‘error‘ : ‘‘} 53 help={passwordError || ‘‘} 54 > 55 {getFieldDecorator(‘password‘, { 56 rules: [{ required: true, message: ‘Please input your Password!‘ }] 57 })( 58 <Input 59 prefix={<Icon type=‘lock‘ style={{ color: ‘rgba(0,0,0,.25)‘ }} />} 60 type=‘password‘ 61 placeholder=‘Password‘ 62 /> 63 )} 64 </Form.Item> 65 <Form.Item> 66 <Button 67 type=‘primary‘ 68 htmlType=‘submit‘ 69 disabled={hasErrors(getFieldsError())} 70 > 71 Log in 72 </Button> 73 </Form.Item> 74 </Form> 75 ) 76 } 77 } 78 79 const WrappedHorizontalLoginForm = Form.create({ name: ‘horizontal_login‘ })( 80 HorizontalLoginForm 81 ) 82 83 export default WrappedHorizontalLoginForm
組件功能分析:
-
-
2-表單提交時做表單項校驗,全部校驗成功則提示登錄,否則提示校驗失敗
-
3-表單項增加前置圖標
組件封裝思路:
- 1-需要一個高階函數hoc FormCreate,用來包裝用戶表單,增加數據管理能力;hoc需要擴展四個功能:getFieldDecorator, getFieldsError, getFieldError, isFieldTouched。獲取字段包裝器方法getFieldDecorator的返回值是個高階函數,接收一個Input組件作為參數,返回一個新的組件。這就是讓一個普通的表單項,變成了帶有擴展功能的表單項(例如:增加該項的校驗規則)
- 2-FormItem組件,負責校驗及錯誤信息的展示,需要保存兩個屬性,校驗狀態和錯誤信息,當前校驗通過時錯誤信息為空
- 3-Input組件,展示型組件,增加輸入框前置icon
- 4-導出FormCreate裝飾後的MForm組件,MForm組件負責樣式布局以及提交控制
組件封裝步驟:
-
-
2-寫一個高階組件FormCreate對MForm進行擴充,使MForm組件擁有數據管理的能力。
-
保存字段選項設置 this.options = {}; 這裏不需要保存為state,因為我們不希望字段選項變化而讓組件重新渲染
-
保存各字段的值 this.state = {}
-
定義方法 getFieldDecorator()(),第一個參數傳遞配置項,第二個參數傳入Input組件;第一個參數包括:當前校驗項、校驗規則 ‘username‘,{rules:[require:true,message:‘請輸入用戶名‘]}
-
在FormCreate中,克隆一份Input組件,並且定義Input的onChange事件。首先這裏需要把已經存在的jsx克隆一份,並修改它的屬性,直接修改屬性是不允許的;這裏在更高級別定義onChange事件,控制元素的值,這樣當組件發生變化時,就不用進行組件之間的來回通信。數據變化交給容器型組件去做,低層級的組件只負責展示即可。
-
-
3-增加提交校驗功能
-
4-增加FormItem組件,在表單項觸發後做實時校驗並提示錯誤信息
-
以下每一步驟都可以獨立運行
-
step1 - 搭建基礎代碼
-
1 import React, { Component } from ‘react‘ 2 3 class MForm extends Component { 4 render() { 5 return ( 6 <div> 7 用戶名:<input type=‘text‘ /> 8 密碼:<input type=‘password‘ /> 9 <button>Log in</button> 10 </div> 11 ) 12 } 13 } 14 15 export default MForm
-
1 import React, { Component } from ‘react‘ 2 3 // hoc: 包裝用戶表單,增加數據管理能力及校驗功能 4 const FormCreate = Comp => { 5 return class extends Component { 6 constructor(props) { 7 super(props) 8 this.options = {} // 保存字段選項設置 9 this.state = {} // 保存各字段的值 10 } 11 12 // 處理表單項輸入事件 13 handleChange = e => { 14 const { name, value } = e.target 15 this.setState( 16 { 17 [name]: value 18 }, 19 () => { 20 // TODO: 處理狀態變化後的校驗 21 // 由於setState是異步的,所以這裏需要在回調函數中處理後續操作 22 // 保證狀態已經完成改變 23 } 24 ) 25 }; 26 27 getFieldDecorator = (field, option) => InputComp => { 28 this.options[field] = option 29 return ( 30 <div> 31 {/* 把已經存在的jsx克隆一份,並修改它的屬性,直接修改屬性是不允許的。 32 這裏在更高級別定義onChange事件,控制元素的值,這樣當組件發生變化時, 33 就不用進行組件之間的來回通信 */} 34 {React.cloneElement(InputComp, { 35 name: field, // 控件name 36 value: this.state[field] || ‘‘, // 控件值 37 onChange: this.handleChange // change事件處理 38 })} 39 </div> 40 ) 41 }; 42 render() { 43 return ( 44 <Comp {...this.props} getFieldDecorator={this.getFieldDecorator} /> 45 ) 46 } 47 } 48 } 49 50 @FormCreate 51 class MForm extends Component { 52 render() { 53 const { getFieldDecorator } = this.props 54 55 return ( 56 <div> 57 用戶名:{getFieldDecorator(‘username‘, { 58 rules: [{ required: true, message: ‘請填寫用戶名‘ }] 59 })(<input type=‘text‘ />)} 60 密碼:{getFieldDecorator(‘password‘, { 61 rules: [{ required: true, message: ‘請填寫密碼‘ }] 62 })(<input type=‘password‘ />)} 63 <button>Log in</button> 64 </div> 65 ) 66 } 67 } 68 69 export default MForm
-
import React, { Component } from ‘react‘ // hoc: 包裝用戶表單,增加數據管理能力及校驗功能 const FormCreate = Comp => { return class extends Component { constructor(props) { super(props) this.options = {} // 保存字段選項設置 this.state = {} // 保存各字段的值 } // 處理表單項輸入事件 handleChange = e => { const { name, value } = e.target this.setState( { [name]: value }, () => { // 處理狀態變化後的校驗 // 由於setState是異步的,所以這裏需要在回調函數中處理後續操作 // 保證狀態已經完成改變 this.validateField(name) } ) }; // 表單項校驗,可以引用async-validator庫來做校驗,這裏為了簡便直接做非空校驗 validateField = field => { // this.options數據格式如下 ↓↓↓ // { // "username": { // "rules": [{ // "required": true, // "message": "請填寫用戶名" // }] // }, // "password": { // "rules": [{ // "required": true, // "message": "請填寫密碼" // }] // } // } const { rules } = this.options[field] const ret = rules.some(rule => { if (rule.required) { if (!this.state[field]) { this.setState({ [field + ‘Message‘]: rule.message }) // this.state數據格式如下 ↓↓↓ // {"username":"","usernameMessage":"","password":"","passwordMessage":""} return true // 校驗失敗,返回true } } }) if (!ret) { // 校驗成功,將錯誤信息清空 this.setState({ [field + ‘Message‘]: ‘‘ }) } return !ret }; // 校驗所有字段 validate = cb => { const rets = Object.keys(this.options).map(field => this.validateField(field) ) // 如果校驗結果數組中全部為true,則校驗成功 const ret = rets.every(v => v === true) cb(ret) }; getFieldDecorator = (field, option) => InputComp => { this.options[field] = option return ( <div> {/* 把已經存在的jsx克隆一份,並修改它的屬性,直接修改屬性是不允許的。 這裏在更高級別定義onChange事件,控制元素的值, 這樣當組件發生變化時,就不用進行組件之間的來回通信 */} {React.cloneElement(InputComp, { name: field, // 控件name value: this.state[field] || ‘‘, // 控件值 onChange: this.handleChange // change事件處理 })} </div> ) }; render() { return ( <Comp {...this.props} getFieldDecorator={this.getFieldDecorator} validate={this.validate} /> ) } } } @FormCreate class MForm extends Component { onSubmit = () => { this.props.validate(isValid => { if (isValid) { alert(‘校驗成功,可以登錄了‘) console.log(this.props.value) } else { alert(‘校驗失敗‘) } }) }; render() { const { getFieldDecorator } = this.props return ( <div> 用戶名:{getFieldDecorator(‘username‘, { rules: [{ required: true, message: ‘請填寫用戶名‘ }] })(<input type=‘text‘ />)} 密碼:{getFieldDecorator(‘password‘, { rules: [{ required: true, message: ‘請填寫密碼‘ }] })(<input type=‘password‘ />)} <button onClick={this.onSubmit}>Log in</button> </div> ) } } export default MForm
- step4 - 增加表單輸入時實時校驗並提示錯誤邏輯,封裝FormItem組件來展示錯誤信息,封裝Input組件,增加前綴圖標。至此,整個MForm組件就編寫完成了!
-
1 import React, { Component } from ‘react‘ 2 import { Icon } from ‘antd‘ 3 4 // hoc: 包裝用戶表單,增加數據管理能力及校驗功能 5 const FormCreate = Comp => { 6 return class extends Component { 7 constructor(props) { 8 super(props) 9 this.options = {} // 保存字段選項設置 10 this.state = {} // 保存各字段的值 11 } 12 13 // 處理表單項輸入事件 14 handleChange = e => { 15 const { name, value } = e.target 16 this.setState( 17 { 18 [name]: value 19 }, 20 () => { 21 // 處理狀態變化後的校驗 22 // 由於setState是異步的,所以這裏需要在回調函數中處理後續操作 23 // 保證狀態已經完成改變 24 this.validateField(name) 25 } 26 ) 27 }; 28 29 // 表單項校驗,可以引用async-validator庫來做校驗,這裏為了簡便直接做非空校驗 30 validateField = field => { 31 // this.options ↓↓↓ 32 // { 33 // "username": { 34 // "rules": [{ 35 // "required": true, 36 // "message": "請填寫用戶名" 37 // }] 38 // }, 39 // "password": { 40 // "rules": [{ 41 // "required": true, 42 // "message": "請填寫密碼" 43 // }] 44 // } 45 // } 46 const { rules } = this.options[field] 47 const ret = rules.some(rule => { 48 if (rule.required) { 49 if (!this.state[field]) { 50 this.setState({ 51 [field + ‘Message‘]: rule.message 52 }) 53 // this.state ↓↓↓ 54 // {"username":"","usernameMessage":"","password":"","passwordMessage":""} 55 return true // 校驗失敗,返回true 56 } 57 } 58 }) 59 if (!ret) { 60 // 校驗成功,將錯誤信息清空 61 this.setState({ 62 [field + ‘Message‘]: ‘‘ 63 }) 64 } 65 return !ret 66 }; 67 68 // 校驗所有字段 69 validate = cb => { 70 const rets = Object.keys(this.options).map(field => 71 this.validateField(field) 72 ) 73 // 如果校驗結果數組中全部為true,則校驗成功 74 const ret = rets.every(v => v === true) 75 cb(ret) 76 }; 77 78 getFieldDecorator = (field, option) => InputComp => { 79 this.options[field] = option 80 return ( 81 <div> 82 {/* 把已經存在的jsx克隆一份,並修改它的屬性,直接修改屬性是不允許的。 83 這裏在更高級別定義onChange事件,控制元素的值, 84 這樣當組件發生變化時,就不用進行組件之間的來回通信 */} 85 {React.cloneElement(InputComp, { 86 name: field, // 控件name 87 value: this.state[field] || ‘‘, // 控件值 88 onChange: this.handleChange, // change事件處理 89 onFocus: this.handleFocus 90 })} 91 </div> 92 ) 93 }; 94 95 // 控件獲取焦點事件 96 handleFocus = e => { 97 const field = e.target.name 98 this.setState({ 99 [field + ‘Focus‘]: true 100 }) 101 } 102 103 // 判斷控件是否被點擊過 104 isFieldTouched = field => !!this.state[field + ‘Focus‘] 105 106 // 獲取控件錯誤提示信息 107 getFieldError = field => this.state[field + ‘Message‘] 108 109 render() { 110 return ( 111 <Comp 112 {...this.props} 113 getFieldDecorator={this.getFieldDecorator} 114 validate={this.validate} 115 isFieldTouched = {this.isFieldTouched} 116 getFieldError = {this.getFieldError} 117 /> 118 ) 119 } 120 } 121 } 122 123 class FormItem extends Component { 124 render() { 125 return ( 126 <div className=‘formItem‘> 127 { this.props.children } 128 { this.props.validateStatus === ‘error‘ && ( 129 <p style={ { color: ‘red‘ } }>{ this.props.help}</p> 130 )} 131 </div> 132 ) 133 } 134 } 135 136 class Input extends Component { 137 render() { 138 return ( 139 <div> 140 {/* 前綴圖標 */} 141 {this.props.prefix} 142 <input {...this.props} /> 143 </div> 144 ) 145 } 146 } 147 148 @FormCreate 149 class MForm extends Component { 150 onSubmit = () => { 151 this.props.validate(isValid => { 152 if (isValid) { 153 alert(‘校驗成功,可以登錄了‘) 154 console.log(this.props.value) 155 } else { 156 alert(‘校驗失敗‘) 157 } 158 }) 159 }; 160 render() { 161 const { getFieldDecorator, isFieldTouched, getFieldError } = this.props 162 const usernameError = isFieldTouched(‘username‘) && getFieldError(‘username‘) 163 const passwordError = isFieldTouched(‘password‘) && getFieldError(‘password‘) 164 165 return ( 166 <div> 167 <FormItem 168 validateStatus={ usernameError ? ‘error‘ : ‘‘ } 169 help={usernameError || ‘‘} 170 > 171 用戶名:{getFieldDecorator(‘username‘, { 172 rules: [{ required: true, message: ‘請填寫用戶名‘ }] 173 })(<Input type=‘text‘ prefix={<Icon type=‘user‘ />} />)} 174 </FormItem> 175 <FormItem 176 validateStatus={ passwordError ? ‘error‘ : ‘‘ } 177 help={passwordError || ‘‘} 178 > 179 密碼:{getFieldDecorator(‘password‘, { 180 rules: [{ required: true, message: ‘請填寫密碼‘ }] 181 })(<Input type=‘password‘ prefix={<Icon type=‘lock‘ />} />)} 182 </FormItem> 183 <button onClick={this.onSubmit}>Log in</button> 184 </div> 185 ) 186 } 187 } 188 189 export default MForm
- index.js
-
import React from ‘react‘ import ReactDOM from ‘react-dom‘ import MForm from ‘./components/MForm‘ ReactDOM.render(<MForm />, document.querySelector(‘#root‘))
最終效果:
總結:
- react的組件是自上而下的擴展,將擴展的能力由上往下傳遞下去,Input組件在合適的時間就可以調用傳遞下來的值。
- react開發組件的原則是:把邏輯控制往上層提,低層級的組件盡量做成傻瓜組件,不接觸業務邏輯。
如何實現Ant design表單組件封裝?