乐趣区

关于前端:编写高质量可维护的代码之优化逻辑判断

64 篇原创好文~
本文首发于政采云前端团队博客:编写高质量可保护的代码之优化逻辑判断
)

if else、switch case 是日常开发中最常见的条件判断语句,这种看似简略的语句,当遇到简单的业务场景时,如果解决不善,就会呈现大量的逻辑嵌套,可读性差并且难以扩大。

编写高质量可保护的代码,咱们先从最小处动手,一起来看看在前端开发过程中,能够从哪些方面来优化逻辑判断?

上面咱们会别离从 JavaScript 语法和 React JSX 语法两个方面来分享一些优化的技巧。

JavaScript 语法篇

嵌套层级优化

function supply(fruit, quantity) {const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
    // 条件 1: 水果存在
    if(fruit) {
        // 条件 2: 属于红色水果
        if(redFruits.includes(fruit)) {console.log('红色水果');
            // 条件 3: 水果数量大于 10 个
            if (quantity > 10) {console.log('数量大于 10 个');
            }
        }
    } else {throw new Error('没有水果啦!');
    }
}

剖析下面的条件判断,存在三层 if 条件嵌套。

如果提前 return 掉有效条件,将 if else 的多重嵌套档次缩小到一层,更容易了解和保护。

function supply(fruit, quantity) {const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
    if(!fruit) throw new Error('没有水果啦'); // 条件 1: 当 fruit 有效时,提前处理错误
    if(!redFruits.includes(fruit)) return; // 条件 2: 当不是红色水果时,提前 return
    
    console.log('红色水果');
    
    // 条件 3: 水果数量大于 10 个
    if (quantity > 10) {console.log('数量大于 10 个');
    }
}

多条件分支的优化解决

当须要枚举值解决不同的业务分支逻辑时,第一反馈是写下 if else?咱们来看一下:

function pick(color) {
  // 依据色彩抉择水果
  if(color === 'red') {return ['apple', 'strawberry']; 
  } else if (color === 'yellow') {return ['banana', 'pineapple'];
  } else if (color === 'purple') {return ['grape', 'plum'];
  } else {return [];
  }
}

在下面的实现中:

  • if else 分支太多
  • if else 更适宜于条件区间判断,而 switch case 更适宜于具体枚举值的分支判断

应用 switch case 优化下面的代码后:

function pick(color) {
  // 依据色彩抉择水果
  switch (color) {
    case 'red':
      return ['apple', 'strawberry'];
    case 'yellow':
      return ['banana', 'pineapple'];
    case 'purple':
      return ['grape', 'plum'];
    default:
      return [];}
}

switch case 优化之后的代码看上去格局参差,思路很清晰,但还是很简短。持续优化:

  • 借助 Object 的 {key: value} 构造,咱们能够在 Object 中枚举所有的状况,而后将 key 作为索引,间接通过 Object.key 或者 Object[key] 来获取内容
const fruitColor = {red: ['apple', 'strawberry'],
    yellow: ['banana', 'pineapple'],
    purple: ['grape', 'plum'],
}
function pick(color) {return fruitColor[color] || [];}
  • 应用 Map 数据结构,真正的 (key, value) 键值对构造;
const fruitColor = new Map()
.set('red', ['apple', 'strawberry'])
.set('yellow', ['banana', 'pineapple'])
.set('purple', ['grape', 'plum']);

function pick(color) {return fruitColor.get(color) || [];}

优化之后,代码更简洁、更容易扩大。

为了更好的可读性,还能够通过更加语义化的形式定义对象,而后应用 Array.filter 达到同样的成果。

const fruits = [{ name: 'apple', color: 'red'}, 
    {name: 'strawberry', color: 'red'}, 
    {name: 'banana', color: 'yellow'}, 
    {name: 'pineapple', color: 'yellow'}, 
    {name: 'grape', color: 'purple'}, 
    {name: 'plum', color: 'purple'}
];

function pick(color) {return fruits.filter(f => f.color == color);
}

应用数组新个性简化逻辑判断

奇妙的利用 ES6 中提供的数组新个性,也能够让咱们更轻松的解决逻辑判断。

多条件判断

编码时遇到多个判断条件时,本能的写下上面的代码(其实也是最能表白业务逻辑的面向过程编码)。

function judge(fruit) {if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry' || fruit === 'cranberries') {console.log('red');
  }
}

然而当 type 将来到 10 种甚至更多时,咱们只能持续增加 || 来保护代码么 ?

试试 Array.includes ~

// 将判断条件抽取成一个数组
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
function judge(type) {if (redFruits.includes(fruit)) {console.log('red');
     }
}

判断数组中是否所有项都满足某条件

const fruits = [{ name: 'apple', color: 'red'},
    {name: 'banana', color: 'yellow'},
    {name: 'grape', color: 'purple'}
  ];

function match() {
  let isAllRed = true;

  // 判断条件:所有的水果都必须是红色
  for (let f of fruits) {if (!isAllRed) break;
    isAllRed = (f.color === 'red');
  }

  console.log(isAllRed); // false
}

下面的实现中,次要是为了解决数组中的所有项都符合条件。

应用 Array.every 能够很容的实现这个逻辑:

const fruits = [{ name: 'apple', color: 'red'},
    {name: 'banana', color: 'yellow'},
    {name: 'grape', color: 'purple'}
  ];

function match() {
  // 条件:所有水果都必须是红色
  const isAllRed = fruits.every(f => f.color == 'red');

  console.log(isAllRed); // false
}

判断数组中是否有某一项满足条件

Array.some,它次要解决的场景是判断数组中是否有一项满足条件。

如果想晓得是否有红色水果,能够间接应用 Array.some 办法:

const fruits = [{ name: 'apple', color: 'red'},
    {name: 'banana', color: 'yellow'},
    {name: 'grape', color: 'purple'}
  ];

// 条件:是否有红色水果 
const isAnyRed = fruits.some(f => f.color == 'red');

还有许多其余数组新个性,比方 Array.find、Array.slice、Array.findIndex、Array.reduce、Array.splice 等,在理论场景中能够依据须要抉择应用。

函数默认值

应用默认参数

const buyFruit = (fruit,amount) => {if(!fruit){return}
  amount = amount || 1;
  console.log(amount)
}

咱们常常须要处理函数外部的一些参数默认值,下面的代码大家都不生疏,应用函数的默认参数,能够很好的帮忙解决这种场景。

const buyFruit = (fruit,amount = 1) => {if(!fruit){return}
  console.log(amount,'amount')
}

咱们能够通过 Babel 的转译来看一下默认参数是如何实现的。

从下面的转译后果能够发现,只有参数为 undefined 时才会应用默认参数。

测试的执行后果如下:

buyFruit('apple','');  // amount
buyFruit('apple',null);  //null amount
buyFruit('apple');  //1 amount

所以应用默认参数的状况下,咱们须要留神的是默认参数 amount=1 并不等同于 amount || 1

应用解构与默认参数

当函数参数是对象时,咱们能够应用解构联合默认参数来简化逻辑。

Before:

const buyFruit = (fruit,amount) => {fruit = fruit || {};
    if(!fruit.name || !fruit.price){return;}
    ...
  amount = amount || 1;
  console.log(amount)
}

After:

const buyFruit = ({name,price}={},amount) => {if(!name || !prices){return;}
  console.log(amount)
}

简单数据解构

当解决比拟简的对象时,解构与默认参数的配合是十分好的,但在一些简单的场景中,咱们面临的可能是更简单的构造。

const oneComplexObj = {
    firstLevel:{
        secondLevel:[{
            name:"",
            price:""
        }]
    }
}

这个时候如果再通过解构去获取对象里的值。

const {
    firstLevel:{secondLevel:[{name,price]=[]}={}} = oneComplexObj;              

可读性就会比拟差,而且须要思考多层解构的默认值以及数据异常情况。

这种状况下,如果我的项目中应用 lodash 库,能够应用其中的 lodash/get 办法。

import lodashGet from 'lodash/get';

const {name,price} = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});

策略模式优化分支逻辑解决

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。

应用场景:策略模式属于对象行为模式,当遇到具备雷同行为接口、行为外部不同逻辑实现的实例对象时,能够采纳策略模式;或者是一组对象能够依据须要动静的抉择几种行为中的某一种时,也能够采纳策略模式;这里以第二种状况作为示例:

Before:

const TYPE = {
    JUICE:'juice',
    SALAD:'salad',
    JAM:'jam'
}
function enjoy({type = TYPE.JUICE,fruits}){if(!fruits || !fruits.length) {console.log('请先洽购水果!');
           return;
    }
  if(type === TYPE.JUICE) {console.log('榨果汁中...');
      return '果汁';
  }
  if(type === TYPE.SALAD) {console.log('做沙拉中...');
      return '拉沙';
  }
  if(type === TYPE.JAM) {console.log('做果酱中...');
      return '果酱';
  }
  return;
}

enjoy({type:'juice',fruits});

应用思路:定义策略对象封装不同行为、提供策略抉择接口,在不同的规定时调用相应的行为。

After:

const TYPE = {
    JUICE:'juice',
    SALAD:'salad',
    JAM:'jam'
}

const strategies = {[TYPE.JUICE]: function(fruits){console.log('榨果汁中...');
        return '果汁';
    },
    [TYPE.SALAD]:function(fruits){console.log('做沙拉中...');
        return '沙拉';
    },
    [TYPE.JAM]:function(fruits){console.log('做果酱中...');
        return '果酱';
    },
}

function enjoy({type = TYPE.JUICE,fruits}) {if(!type) {console.log('请间接享受!');
           return;
    }
    if(!fruits || !fruits.length) {console.log('请先洽购水果!');
           return;
    }
    return strategies[type](fruits);
}

enjoy({type:'juice',fruits});

框架篇之 React JSX 逻辑判断优化

JSX 是一个看起来很像 XML 的 JavaScript 语法扩大。个别在 React 中应用 JSX 来形容界面信息,ReactDOM.render() 将 JSX 界面信息渲染到页面上。

在 JSX 中反对 JavaScript 表达式,日常很常见的循环输入子组件、三元表达式判断、再简单一些间接形象出一个函数。

在 JSX 中写这么多 JavaScript 表达式,整体代码看起来会有点儿芜杂。试着优化一下!

JSX-Control-Statements

JSX-Control-Statements 是一个 Babel 插件,它扩大了 JSX 的能力,反对以标签的模式解决条件判断、循环。

If 标签

<If> 标签内容只有在 condition 为 true 时才会渲染,等价于最简略的三元表达式。

Before:

{condition() ? 'Hello World!' : null }   

After:

<If condition={condition() }>Hello World!</If>   

留神:<Else /> 已被废除,简单的条件判断能够应用 <Choose> 标签。

Choose 标签

<Choose> 标签下包含至多一个 <When> 标签、可选的 <Otherwise> 标签。

<When> 标签内容只有在 condition 为 true 时才会渲染,相当于一个 if 条件判断分支。

<Otherwise> 标签则相当于最初的 else 分支。

Before:

{test1 ? <span>IfBlock1</span> : test2 ? <span>IfBlock2</span> : <span>ElseBlock</span>}

After:

<Choose>
  <When condition={test1}>
    <span>IfBlock1</span>
  </When>
  <When condition={test2}>
    <span>IfBlock2</span>
  </When>
  <Otherwise>
    <span>ElseBlock</span>
  </Otherwise>
</Choose>

For 标签

<For> 标签须要申明 of、each 属性。

of 接管的是能够应用迭代器拜访的对象。

each 代表迭代器拜访时的以后指向元素。

Before:

{(this.props.items || []).map(item => {return <span key={ item.id}>{item.title}</span>
  })
}

After:

<For each="item" of={this.props.items}>
   <span key={item.id}>{item.title}</span>
</For>

留神:<For> 标签不能作为根元素。

With 标签

<With> 标签提供变量传参的性能。

Before:

renderFoo = (foo) => {return <span>{ foo}</span>;
}

// JSX 中表达式调用
{this.renderFoo(47)
}

After:

<With foo={47}>
  <span>{foo}</span>
</With>

应用这几种标签优化代码,能够缩小 JSX 中存在的显式 JavaScript 表达式,使咱们的代码看上去更简洁,然而这些标签封装的能力,在编译时须要转换为等价的 JavaScript 表达式。

留神:具体 babel-plugin-jsx-control-statements 插件的应用见第三篇参考文章;Vue 框架曾经通过指令的模式反对 v-if、v-else-if、v-else、v-show、slot 等。

总结

以上咱们总结了一些常见的逻辑判断优化技巧。当然,编写高质量可保护的代码,除了逻辑判断优化,还须要有清晰的正文、含意明确的变量命名、正当的代码构造拆分、逻辑分层解耦、以及更高层次的贴合业务的逻辑形象等等,置信各位在这方面也有本人的一些心得,欢送一起留言探讨~

参考文献

  • 5 Tips to Write Better Conditionals in JavaScript
  • stop-putting-so-many-if-statements-in-your-javascript
  • JSX Control Statements

招贤纳士

政采云前端团队(ZooTeam),一个年老富裕激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员形成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、性能体验、云端利用、数据分析及可视化等方向进行技术摸索和实战,推动并落地了一系列的外部技术产品,继续摸索前端技术体系的新边界。

如果你想扭转始终被事折腾,心愿开始能折腾事;如果你想扭转始终被告诫须要多些想法,却无从破局;如果你想扭转你有能力去做成那个后果,却不须要你;如果你想扭转你想做成的事须要一个团队去撑持,但没你带人的地位;如果你想扭转既定的节奏,将会是“5 年工作工夫 3 年工作教训”;如果你想扭转原本悟性不错,但总是有那一层窗户纸的含糊… 如果你置信置信的力量,置信平凡人能成就不凡事,置信能遇到更好的本人。如果你心愿参加到随着业务腾飞的过程,亲手推动一个有着深刻的业务了解、欠缺的技术体系、技术发明价值、影响力外溢的前端团队的成长历程,我感觉咱们该聊聊。任何工夫,等着你写点什么,发给 ZooTeam@cai-inc.com

退出移动版