1. 程式人生 > >如何完整的編寫一個React通用元件?

如何完整的編寫一個React通用元件?

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;