如何完整的編寫一個React通用元件?
阿新 • • 發佈:2018-12-17
React元件概念想必大家都熟悉了,但是在業務開發過程中,面對通用的業務,怎麼編寫一個通用的React業務元件呢?本文以實際案例說明,如何編寫一個通用的業務元件。
案例背景
實現一個如圖所示的元件,可以新增對元件進行新增和刪除
實現思路
- 實現元件的介面樣式
- 實現元件的基本功能
- 考慮通用型和可擴充套件性
實現基本的介面樣式
實現上面的介面展示,需要三個方面的東西
- 一個文字框
- 一個➖操作
- 一個➕操作 jrs可以自己編寫樣式,也可以引用通用的元件,本文偷個懶,決定引用現成的antd的元件
import React, { Component } from 'react'; import {Input, Icon} from 'antd'; import PropTypes from 'prop-types'; class List extends Component{ render() { let iconStyle = { del: { color: 'red', marginLeft: '0.2%' }, add: { color: 'grey', marginLeft: '0.2%' } } return <div style = {{display: 'flex', marginLeft:'10px'}}> <Input/> <Icon type="minus-circle" theme="outlined" style = {iconStyle.del}/> <Icon type="plus-circle" theme="outlined" style = {iconStyle.add}/> </div> } }
上面就把基本的樣式搞定了,這離我們想要的還缺一些基本的功能
實現基本功能
元件處於編輯狀態的時候
- 點選➕,增加元件
- 點選➖,減少相應元件
- 當只有一個元件的時候,只顯示➕ 元件處於非編輯狀態的時候
- 輸入框不能編輯
- ➕和➖都不展示 實現的思路就是,元件內部維護一個list,然後每次增加和刪除的時候,對list進行增加和刪除,然後渲染整個元件。
class List extends Component{ constructor(props) { super(props); this.state = { list : [1] } } add = (key) => () => { let list = this.state.list; list.push(1); this.setState({ list: list }); } del = (key) => () => { let list = this.state.list; list.splice(key, 1, 0); this.setState({ list: list }); } renderList = () => { let list = this.state.list; let renderList = []; let len = list.length; let delShow = list.reduce((total, value) => total + value); let iconStyle = { del: { color: 'red', marginLeft: '0.2%' }, add: { color: 'grey', marginLeft: '0.2%' } } for (let i =0; i < len; i++) { list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}> <Input/> {delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>} <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/> </div>) } return renderList; } render() { return this.renderList(); } }
實現了上述元件,可以進行新增和刪除了,但是這個元件具備一個完整的元件還需要一些功能
- 可以獲取元件的值
- 可以設定元件的預設值
- 元件可以設定非編輯狀態
- 可以設定元件的樣式
class List extends Component{ constructor(props) { super(props); this.state = { list : [1] } } static defaultProps = { iconStyle: { del: { color: 'red', marginLeft: '0.2%' }, add: { color: 'grey', marginLeft: '0.2%' } }, // 提供預設的樣式選擇 layoutStyle: { display: 'flex', width: '40%', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'flex-start' }, // 通過disabled控制組件的狀態 disabled: false } add = (key) => () => { let list = this.state.list; list.push(1); this.setState({ list: list }); } del = (key) => () => { let list = this.state.list; list.splice(key, 1, 0); this.setState({ list: list }); // console.log('exce del action', key); } // 獲取元件的值,本文中預設所有元件都實現getValue方法,然後通過ref方法獲得 getValue = () => { let list = this.state.list; let len = list.length; let value = []; for(let i = 0; i< len; i++) { if(list[i]) { value.push(this.refs[i].getValue()) } } return value; } renderList = (value) => { let list = this.state.list; let renderList = []; let len = list.length; let delShow = list.reduce((total, value) => total + value); let { iconStyle, disabled} = this.props; for (let i =0; i < len; i++) { list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}> <Input ref= {i} defaultValue = {value && value[i]} disabled = {disabled}/> {!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>} {!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>} </div>) } return renderList; } render() { let { value, layoutStyle } = this.props; return <div style = {layoutStyle}> // 通過value設定元件的預設值 {this.renderList(value)} </div> } }
經過上面的編寫,終於完成了元件的基本樣式和功能,基本上是夠用的了,但是元件還存在一些問題,通用性不夠。
實現更通用的元件
- 如果不僅僅展示一個input,可能是任意其他任何一個元件怎麼辦
- 如果想在元件中間新增,一些特別的字元或者元件,比如且或非邏輯怎麼辦? 先來思考第一個 如果元件不是input而是其他的,怎麼辦?怎麼獲取它的值怎麼設定它的狀態? 如果換成其他的元件,當然很簡單,在多一個引數,把這個引數作為元件就可以。 ok,這樣是可行的,但是怎麼獲取它的值呢?以及怎麼設定他的狀態? 簡單來說,就是怎麼把 ref = i 注入到元件中去? 答:實現一個高階元件就可以了啊。
class List extends Component{
constructor(props) {
super(props);
this.state = {
list : [1]
}
}
static defaultProps = {
iconStyle: {
del: {
color: 'red', marginLeft: '0.2%'
},
add: {
color: 'grey', marginLeft: '0.2%'
}
},
layoutStyle: {
display: 'flex',
width: '40%',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start'
},
disabled: false
}
add = (key) => () => {
let list = this.state.list;
list.push(1);
this.setState({
list: list
});
}
del = (key) => () => {
let list = this.state.list;
list.splice(key, 1, 0);
this.setState({
list: list
});
// console.log('exce del action', key);
}
getValue = () => {
let list = this.state.list;
let len = list.length;
let value = [];
for(let i = 0; i< len; i++) {
if(list[i]) {
value.push(this.refs[i].getValue())
}
}
return value;
}
renderList = (value) => {
let list = this.state.list;
let renderList = [];
let len = list.length;
let delShow = list.reduce((total, value) => total + value);
// InputComponent 就是你的輸入元件 ,after則是你需要插入的字尾元件
let { iconStyle, disabled, InputComponent, after} = this.props;
for (let i =0; i < len; i++) {
list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
<InputComponent ref = {i} defaultValue = {value && value[i]} disabled = {disabled}/>
{delShow > 1 && after}
{!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
{!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>}
</div>)
}
return renderList;
}
render() {
let { value, layoutStyle } = this.props;
return <div style = {layoutStyle}>
{this.renderList(value)}
</div>
}
}
至此,整個功能就編寫完成了。 那麼還缺一點東西,那就是引數說明
List.propTypes = {
value: PropTypes.array,
layoutStyle: PropTypes.object,
iconStyle: PropTypes.object,
InputComponent: PropTypes.element.isRequired,
after: PropTypes.node,
disabled: PropTypes.bool
};
附錄
附上完整程式碼
import React, { Component } from 'react';
import {Input, Icon} from 'antd';
import PropTypes from 'prop-types';
class List extends Component{
constructor(props) {
super(props);
this.state = {
list : [1]
}
}
static defaultProps = {
iconStyle: {
del: {
color: 'red', marginLeft: '0.2%'
},
add: {
color: 'grey', marginLeft: '0.2%'
}
},
layoutStyle: {
display: 'flex',
width: '40%',
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start'
},
disabled: false
}
add = (key) => () => {
let list = this.state.list;
list.push(1);
this.setState({
list: list
});
}
del = (key) => () => {
let list = this.state.list;
list.splice(key, 1, 0);
this.setState({
list: list
});
// console.log('exce del action', key);
}
getValue = () => {
let list = this.state.list;
let len = list.length;
let value = [];
for(let i = 0; i< len; i++) {
if(list[i]) {
value.push(this.refs[i].getValue())
}
}
return value;
}
renderList = (value) => {
let list = this.state.list;
let renderList = [];
let len = list.length;
let delShow = list.reduce((total, value) => total + value);
let { iconStyle, disabled, InputComponent, after} = this.props;
for (let i =0; i < len; i++) {
list[i] && renderList.push(<div key = {i} style = {{display: 'flex', marginLeft:'10px'}}>
{/* <Input ref= {i} defaultValue = {value && value[i]} disabled = {disabled}/> */}
<InputComponent ref = {i} defaultValue = {value && value[i]} disabled = {disabled}/>
{delShow > 1 && after}
{!disabled && delShow > 1 && <Icon type="minus-circle" theme="outlined" style = {iconStyle.del} onClick = {this.del(i)}/>}
{!disabled && <Icon type="plus-circle" theme="outlined" style = {iconStyle.add} onClick = {this.add(i)}/>}
</div>)
}
return renderList;
}
render() {
let { value, layoutStyle } = this.props;
return <div style = {layoutStyle}>
{this.renderList(value)}
</div>
}
}
List.propTypes = {
value: PropTypes.array,
layoutStyle: PropTypes.object,
iconStyle: PropTypes.object,
InputComponent: PropTypes.element.isRequired,
after: PropTypes.node,
disabled: PropTypes.bool
};
export default List;