前端编码实践 for React

115 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

前言

编码是一门技术

既然是技术,那就不能闭门造车,必须要深耕苦练,而这边文章即是分享我多年来在项目中所踩的坑

希望对大家有所帮助

资源引用和接口调用

  • export导出的方法禁止使用箭头函数 统一使用function 避免循环引用时的undefined的问题 (以function定义的函数将被提前声明)

  • 在进行新的api调用时 需要检查是否有现存的同样的接口调用 若有2处或超过两处 需要进行封装 将其余处一并更改并回归

  • 在批量调用某接口时 严禁使用计数器  定时器轮询等方法 应使用Promise.all

let promises = tidArr.filter(trade => trade.post_fee != '0.00').map(trade => {

            return new Promise((resolve,reject) => {

                taobaoTradePostageUpdate({

                    query: {

                        tid: trade.tid,

                        post_fee: 0

                    },

                    callback: (rsp) => {

                        this.successIds.push({tid: trade.tid,trade: rsp.trade_postage_update_response.trade});

                    },

                    errCallback: (msg) => {

                        this.errorMsg.push({tid: trade.tid,msg: msg});

                    }

                })

            })

        });

        Promise.all(promises).then(()=>{

            let code = '';

            if(tidArr.length == this.successIds.length){ //选择的订单本来邮费全部为0

                MsgToast('success',`操作成功`,2000);

                code = '200';

            }else{

                let errMsg = [];

                let msg = [];

                //去掉重复的报错原因

                this.errorMsg.map((item,index)=>{

                    if(isEmpty(errMsg[item.code])){

                        errMsg[item.code] = 1;

                        msg.push(item.msg.msg);

                    }

                })

                let msgStr = msg.join(';');

                ErrorDialog('温馨提示',`有 ${this.errorMsg.length} 笔订单操作失败`,`${msgStr}`);

                code = '500';

            }

            callback({code:code,successIds:this.successIds});

        })

render

禁止在render中干写静态样式,静态样式必须放到scss中,允许在render中写动态样式 (style中的参数只能是变量)

变量

  • 变量/函数必须做到顾名思义 若对应的变量/函数在业务变动后意义产生变化 必须及时更改.

  • 自己定义的局部变量/函数遵循camelCasing

常量

  • 常用的字符串 或作为判断条件的字符串尽量写在固定的变量中。保证调用的时候不会出现拼写错误。并降低重命名和重构成本。

  • 常量、常量池使用全部大写加下划线命名 如 const API_LIST={...}

//very bad

if(printMode=='EXISTCODE'){

    this.initPrintExistCode();

}else if(printMode='NEWCODE'){

    this.initPrintNewCode();

}else{

    this.initPrintNewCode();   

}


//bad

switch (printMode){

    case 'EXISTCODE':

        this.initPrintExistCode();

        break;

    case 'NEWCODE':

        this.initPrintNewCode();

        break;

    case 'GETCODEAGAIN':

        this.initPrintNewCode();

        break;

}

//good

switch (printMode){

    case PRINT_MODE.EXISTCODE:

        this.initPrintExistCode();

        break;

    case PRINT_MODE.NEWCODE:

        this.initPrintNewCode();

        break;

    case PRINT_MODE.GETCODEAGAIN:

        this.initPrintNewCode();

        break;

}

CSS

在css命名中 禁止使用camelCasing、PascalCasing、下划线分割等 应使用短横杠-分割


/*bad*/

.tradeList{

}

/*bad*/

.trade_list{

}
/*good*/

.trade-list{

}

禁止在组件内的scss文件中写顶级样式

/* very bad */

.some-dialog{

    &

}

.next-dialog{

        width:300px;

    }

每个Component必须有一个最外面的总的className ,在写scss时 必须将对应的该类的scss放在总的className之下


export class TradeCell extends React.Component{

    render(){

        return <div className='trade-cell'>

           <div className="trade-cell-header">

                <Brief trade={trade}/>

            </div>

        </div>;

    }

}

.trade-cell {

    .trade-cell-header {

      min-height: 32px;

      .trade-card-brief {

        display: block;

      }

    }

  }

禁止将函数的对象类型的默认参数定义到函数外 如 const param1={name:123} function bar(param=param1) 必须将对象类型的默认参数写到函数参数表中.

const defaultQuery=

export function getFullinfo(tid,query={

    api:'taobao.trade.fullinfo.get',

    param:{

        tid:'',

        status:'',

        buyer_nick:'',

    }

}){

    query.param.tid=tid;

    api({

        args:query.param,

        callback:()=>{

            query.param.tid

            //...

        },

        errCallback:()=>{

            //...

        }

    })

}

遍历 循环

  • 类似于for(i=0;i<length;i++){}的 下文统称为fori

  • 避免在遍历数组时使用fori 应使用Array.map或Array.forEach 过滤应使用Array.filter

  • 避免在遍历对象时使用for(let key in object) 推荐使用Object.keys(object).map(key=>{})

  • 在映射数组时应使用Array.map

//bad

let tids = [];

for(let i in trades){

    tids.push(trades[i].tid);

}

tids=tids.toString()

//good

let tidsStr=trades.map(data=>data.tid).join(',');

  • 在过滤数组时时应使用Array.filter
//bad

for(let order of trade.orders.order){

    if(order.status=='WAIT_BUYER_PAY'){

        orderArr.push(order);

    }

}
//good

let orderArr=trade.orders.order.filter(order=>order.status=='WAIT_BUYER_PAY')

\


#### 在不可避免的使用fori时 若数组内为对象 禁止在多处取索引 即多处调用arr[i] 必须对该对象进行封装如let currentElement=arr[i];
//bad

  for(let i in printData){

                printData[i].buyerMessage = this.makeFilterKeywords(printData[i].buyerMessage,filterKeywords);

                printData[i].sellerMemo = this.makeFilterKeywords(printData[i].sellerMemo,filterKeywords);

                for(let j in printData[i].orders){

                    printData[i].orders[j].itemGoodsTitle = this.makeFilterKeywords(printData[i].orders[j].itemGoodsTitle,filterKeywords);

                    printData[i].orders[j].itemOuterIid = this.makeFilterKeywords(printData[i].orders[j].itemOuterIid,filterKeywords);

                    printData[i].orders[j].itemSkuPropertiesName = this.makeFilterKeywords(printData[i].orders[j].itemSkuPropertiesName,filterKeywords);

                }

   }

//still bad

  for(let i in printData){

        let printItem= printData[i];

        printItem.buyerMessage = this.makeFilterKeywords(printItem.buyerMessage,filterKeywords);

        printItem.sellerMemo = this.makeFilterKeywords(printItem.sellerMemo,filterKeywords);

        for(let j in printItem.orders){

            let order=printItem.orders[j];

            order.itemGoodsTitle = this.makeFilterKeywords(order.itemGoodsTitle,filterKeywords);

            order.itemOuterIid = this.makeFilterKeywords(order.itemOuterIid,filterKeywords);

            order.itemSkuPropertiesName = this.makeFilterKeywords(order.itemSkuPropertiesName,filterKeywords);

        }

   }
//good

printData.map(printItem=>{

     printItem.buyerMessage = this.makeFilterKeywords(printItem.buyerMessage,filterKeywords);

     printItem.sellerMemo = this.makeFilterKeywords(printItem.sellerMemo,filterKeywords);

     printItem.orders.map(order=>{

           order.itemGoodsTitle = this.makeFilterKeywords(order.itemGoodsTitle,filterKeywords);

           order.itemOuterIid = this.makeFilterKeywords(order.itemOuterIid,filterKeywords);

           order.itemSkuPropertiesName = this.makeFilterKeywords(order.itemSkuPropertiesName,filterKeywords);

     })

})
  • 避免进行时间复杂度为o(n2)级别的匹配 若要进行匹配 推荐在匹配前建立索引后取索引
//bad

for(let i=0;i<trades.length;i++){

    let trade=trades[i];

    for(let j=0;j<fullinfos.length;j++){

        let fullinfo=fullinfos[j];

        if(trade.tid==fullinfo.tid){

            Object.assign(trade,fullinfo);

        }

    }

}
//good

let tradesIndexedByTid={};

trades.map(trade=>{

    tradesIndexedByTid[trade.tid]=trade;

})

fullinfos.map(fullinfo=>{

    let trade=tradesIndexedByTid[fullinfo.tid];

    if(trade){

        Object.assign(trade,fulllinfo);

    }

})
  • 禁止使用包括fori,map,foreach 之内的所有方法进行o(n)查找,若不可避免的需要查找 请使用find 如 dataSource.find(trade=>trade.tid='???')
//bad

for(trade in trades){

    if(trade.tid==tid)

    {

        //....

    }

}
//good

let trade=trades.find(trade=>trade.tid=tid)

其他

  • 避免使用window.dispatchEvent 若需要使用 必须封装. 严禁在component的文件中看到直接调用window.dispatchEvent

  • 禁止在JSX内写超过5行的function

  • 禁止使用getElementByClassName getElementById getElementByTagName等dom操作

<div

//这里完全可以使用css的hover解决

                className    = 'div-flex-column'

                onMouseEnter = {(value) => {

                    let hovers = document.getElementsByClassName('hover' + dataSource.tid);

                    for(let node in hovers){

                        if (hovers[node].nodeType === 1) {

                            if (checkboxGroup.includes(dataSource.tid)) {

                                hovers[node].style.backgroundColor = '#FFF8E1';

                            } else {

                                hovers[node].style.backgroundColor = '#F5F5F5';

                            }

                        }

                    }

                    let svgs = document.getElementsByClassName('svg-display' + dataSource.tid);

                    for(let node in svgs){

                        if (svgs[node].nodeType === 1) {

                            svgs[node].style.display = 'inline';

                        }

                    }

                }}

                onMouseLeave={(value)=>{

                    let hovers = document.getElementsByClassName('hover' + dataSource.tid);

                    for(let node in hovers){

                        if (hovers[node].nodeType === 1) {

                            if (checkboxGroup.includes(dataSource.tid)) {

                                hovers[node].style.backgroundColor = '#FFF8E1';

                            } else {

                                hovers[node].style.backgroundColor = '#FFFFFF';

                            }

                        }

                    }

                    let svgs = document.getElementsByClassName('svg-display' + dataSource.tid);

                    for(let node in svgs){

                        if (svgs[node].nodeType === 1) {

                            svgs[node].style.display = 'none';

                        }

                    }

                }}

                style={{alignItems: 'flex-start', justifyContent: 'flex-start', flex: 1, width: width - 15}}

            >

DRY (don't repeat yourself)

  • 避免复制代码 严禁超过5行的js/jsx代码复制,须一并更改,封装并回归.

  • 严禁复制文件,对于公用的逻辑需要提取并封装回归。

redux的使用

  • 禁止使用thunk 在不需要SSR的情况下使用thunk纯粹是自讨苦吃没事找事

  • 禁止传递dispatch

  • reducer中的state要保持正交性 在能通过某个或某几个state能推导出另一个state时,这个能被推导出的state应该被删除并使用计算函数 getXXX() 代替

组件间的状态管理

  • 组件间尽量将状态交由最外层组件管理 ,子组件只负责通过得到的props渲染并回调

  • 严禁将父组件的props传递为子组件的state 并在willReceiveProps中更新

//bad

    constructor(props) {

        super(props);

        this.state = {

            dataSource:this.props.dataSource,

        }

    }

    componentWillReceiveProps(nextProps){

        this.setState({

            dataSource:nextProps.dataSource,

        })

    }

    onClick=()=>{

        this.setState({

            dataSource://.....  这个时候让父组件的dataSource怎么办 要更新吗 更新以后还会触发WillReceiveProps

        })

    }
  • 严禁使用localStorage传递返回值

FixedDataTable

  • 在单页数据量小于300行时 禁止使用FixedDataTable

  • fixedDataTable 适合于百万级数据量的显示,但是没有平滑滚动效果,有一种列表在跳动的感觉。

  • 在1000个左右的数据量时,应使用DOM渲染,DOM自带平滑滚动,流畅度大幅提升,在发现由DOM渲染的列表滚动有卡顿情况出现时 可以添加css属性 will-change:transform

ReactDimetions

  • 除FixedDataTable外面一层,禁止使用reactDimetions,需要转换为对应的css

  • 在不可避免的使用reactDimetions时禁止在ReactDimetions中使用window.innerHeight

//bad

export default Dimensions({

    getHeight: function() {

        return window.innerHeight - 215;

    },

    getWidth: function() {

        return window.innerWidth - 34;

    }

})(BlackTable));
// good
export default Dimensions({

    getHeight: function(e) {

        return e.clientHeight;

    },

    getWidth: function(e) {

        return e.clientWidth;

    }

})(BlackTable));

dialog

  • 禁止在render中写任何有关于是否显示对话框的逻辑 需要使用对话框时 推荐在调用对话框代码的上下文调用showDialog() 或者其他能够在上下文直接渲染出独立的对话框的函数 如Modal.confirm

  • 严禁在显示对话框的api内使用removeChild删除对话框 应使用ReactDOM.unmountComponentAtNode (已封装在showDialog中)

//bad

    let func = () => {

        let element = document.getElementById('GiveMeFiveDialog');

        if (element) element.parentNode.removeChild(element);

    }

    let element = document.getElementById('GiveMeFiveDialog');

    if (element) {

    } else {

        let div = document.createElement('div');

        div.id = 'GiveMeFiveDialog';

        document.getElementById('container').appendChild(div);

        ReactDom.render(

            <GiveMeFive type = { type } action = { action } onClose = { func }/>,

            document.getElementById('GiveMeFiveDialog')

        );

    }
//good

showDialog(<GiveMeFive type = { type } action = { action }/>)