持续创作,加速成长!这是我参与「掘金日新计划 · 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 }/>)