原文地址

前言

if else、switch case 是日常开发中最常见的条件判断语句,这种看似简单的语句,当遇到复杂的业务场景时,如果处理不善,就会出现大量的逻辑嵌套,可读性差并且难以扩展。
编写高质量可维护的代码,我们先从最小处入手,一起来看看在前端开发过程中,可以从哪些方面来优化逻辑判断?
下面我们会分别从 JavaScript 语法和 React JSX 语法两个方面来分享一些优化的技巧。

JavaScript 语法篇

嵌套层级优化

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

分析上面的条件判断,存在三层 if 条件嵌套。
如果提前 return 掉无效条件,将 if else的多重嵌套层次减少到一层,更容易理解和维护。

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

多条件分支的优化处理

当需要枚举值处理不同的业务分支逻辑时, 第一反应是写下 if else ?我们来看一下:

  1. function pick(color) {
  2. // 根据颜色选择水果
  3. if(color === 'red') {
  4. return ['apple', 'strawberry'];
  5. } else if (color === 'yellow') {
  6. return ['banana', 'pineapple'];
  7. } else if (color === 'purple') {
  8. return ['grape', 'plum'];
  9. } else {
  10. return [];
  11. }
  12. }

在上面的实现中:

  • if else 分支太多
  • if else 更适合于条件区间判断,而 switch case 更适合于具体枚举值的分支判断
    使用 switch case 优化上面的代码后:
    1. function pick(color) {
    2. // 根据颜色选择水果
    3. switch (color) {
    4. case 'red':
    5. return ['apple', 'strawberry'];
    6. case 'yellow':
    7. return ['banana', 'pineapple'];
    8. case 'purple':
    9. return ['grape', 'plum'];
    10. default:
    11. return [];
    12. }
    13. }
    switch case 优化之后的代码看上去格式整齐,思路很清晰,但还是很冗长。继续优化:
    借助 Object 的 { key: value } 结构,我们可以在 Object 中枚举所有的情况,然后将 key 作为索引,直接通过 Object.key 或者 Object[key] 来获取内容
    1. const fruitColor = {
    2. red: ['apple', 'strawberry'],
    3. yellow: ['banana', 'pineapple'],
    4. purple: ['grape', 'plum'],
    5. };
    6. function pick(color) {
    7. return fruitColor[color] || [];
    8. }
    使用 Map 数据结构,真正的 (key, value) 键值对结构 ;
    ```javascript const fruitColor = new Map() .set(‘red’, [‘apple’, ‘strawberry’]) .set(‘yellow’, [‘banana’, ‘pineapple’]) .set(‘purple’, [‘grape’, ‘plum’]);

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

  1. 优化之后,代码更简洁、更容易扩展。<br />为了更好的可读性,还可以通过更加语义化的方式定义对象,然后使用 Array.filter 达到同样的效果。
  2. ```javascript
  3. const fruits = [
  4. { name: 'apple', color: 'red' },
  5. { name: 'strawberry', color: 'red' },
  6. { name: 'banana', color: 'yellow' },
  7. { name: 'pineapple', color: 'yellow' },
  8. { name: 'grape', color: 'purple' },
  9. { name: 'plum', color: 'purple' }
  10. ];
  11. function pick(color) {
  12. return fruits.filter(f => f.color === color);
  13. }

使用数组新特性简化逻辑判断

巧妙的利用 ES6 中提供的数组新特性,也可以让我们更轻松的处理逻辑判断。

多条件判断

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

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

但是当 type 未来到 10 种甚至更多时, 我们只能继续添加 || 来维护代码么 ?
试试 Array.includes ~

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

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

  1. const fruits = [
  2. { name: 'apple', color: 'red' },
  3. { name: 'banana', color: 'yellow' },
  4. { name: 'grape', color: 'purple' }
  5. ];
  6. function match() {
  7. let isAllRed = true;
  8. // 判断条件:所有的水果都必须是红色
  9. for (let f of fruits) {
  10. if (!isAllRed) break;
  11. isAllRed = (f.color === 'red');
  12. }
  13. console.log(isAllRed); // false
  14. }

上面的实现中,主要是为了处理数组中的所有项都符合条件。
使用 Array.every 可以很容的实现这个逻辑:

  1. const fruits = [
  2. { name: 'apple', color: 'red' },
  3. { name: 'banana', color: 'yellow' },
  4. { name: 'grape', color: 'purple' }
  5. ];
  6. function match() {
  7. // 条件:所有水果都必须是红色
  8. const isAllRed = fruits.every(f => f.color === 'red');
  9. console.log(isAllRed); // false
  10. }

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

Array.some ,它主要处理的场景是判断数组中是否有一项满足条件。
如果想知道是否有红色水果,可以直接使用 Array.some 方法:

  1. const fruits = [
  2. { name: 'apple', color: 'red' },
  3. { name: 'banana', color: 'yellow' },
  4. { name: 'grape', color: 'purple' }
  5. ];
  6. // 条件:是否有红色水果
  7. const isAnyRed = fruits.some(f => f.color == 'red');

还有许多其他数组新特性,比如 Array.find、Array.slice、Array.findIndex、Array.reduce、Array.splice 等,在实际场景中可以根据需要选择使用。

函数默认值

使用默认参数

  1. //before
  2. const buyFruit = (fruit,amount) => {
  3. if(!fruit){
  4. return
  5. }
  6. amount = amount || 1;
  7. console.log(amount)
  8. }
  9. //after
  10. const buyFruit = (fruit,amount = 1) => {
  11. if(!fruit){
  12. return
  13. }
  14. console.log(amount,'amount')
  15. }

我们可以通过 Babel 的转译来看一下默认参数是如何实现的。
image.png
从上面的转译结果可以发现,只有参数为 undefined 时才会使用默认参数。

使用解构与默认参数

当函数参数是对象时,我们可以使用解构结合默认参数来简化逻辑。

  1. //before
  2. const buyFruit = (fruit,amount) => {
  3. fruit = fruit || {};
  4. if(!fruit.name || !fruit.price){
  5. return;
  6. }
  7. ...
  8. amount = amount || 1;
  9. console.log(amount)
  10. }
  11. //after
  12. const buyFruit = ({ name,price }={},amount) => {
  13. if(!name || !prices){
  14. return;
  15. }
  16. console.log(amount)
  17. }

复杂数据解构

当处理比较简的对象时,解构与默认参数的配合是非常好的,但在一些复杂的场景中,我们面临的可能是更复杂的结构。

  1. const oneComplexObj = {
  2. firstLevel: {
  3. secondLevel: [{
  4. name: '',
  5. price: '',
  6. }],
  7. },
  8. };

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

  1. const {
  2. firstLevel: {
  3. secondLevel: [{ name, price }] = [],
  4. } = {},
  5. } = oneComplexObj;

可读性就会比较差,而且需要考虑多层解构的默认值以及数据异常情况。
这种情况下,如果项目中使用 lodash 库,可以使用其中的 lodash/get 方法。

  1. import lodashGet from 'lodash/get';
  2. const { name,price } = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});

策略模式优化分支逻辑处理

策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
使用场景:策略模式属于对象行为模式,当遇到具有相同行为接口、行为内部不同逻辑实现的实例对象时,可以采用策略模式;或者是一组对象可以根据需要动态的选择几种行为中的某一种时,也可以采用策略模式;这里以第二种情况作为示例:
Before:

  1. const TYPE = {
  2. JUICE: 'juice',
  3. SALAD: 'salad',
  4. JAM: 'jam',
  5. };
  6. function enjoy({ type = TYPE.JUICE, fruits }) {
  7. if (!fruits || !fruits.length) {
  8. console.log('请先采购水果!');
  9. return;
  10. }
  11. if (type === TYPE.JUICE) {
  12. console.log('榨果汁中...');
  13. return '果汁';
  14. }
  15. if (type === TYPE.SALAD) {
  16. console.log('做沙拉中...');
  17. return '拉沙';
  18. }
  19. if (type === TYPE.JAM) {
  20. console.log('做果酱中...');
  21. return '果酱';
  22. }
  23. }
  24. enjoy({ type: 'juice', fruits });

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

  1. const TYPE = {
  2. JUICE: 'juice',
  3. SALAD: 'salad',
  4. JAM: 'jam',
  5. };
  6. const strategies = {
  7. [TYPE.JUICE](fruits) {
  8. console.log('榨果汁中...');
  9. return '果汁';
  10. },
  11. [TYPE.SALAD](fruits) {
  12. console.log('做沙拉中...');
  13. return '沙拉';
  14. },
  15. [TYPE.JAM](fruits) {
  16. console.log('做果酱中...');
  17. return '果酱';
  18. },
  19. };
  20. function enjoy({ type = TYPE.JUICE, fruits }) {
  21. if (!type) {
  22. console.log('请直接享用!');
  23. return;
  24. }
  25. if (!fruits || !fruits.length) {
  26. console.log('请先采购水果!');
  27. return;
  28. }
  29. return strategies[type](fruits);
  30. }
  31. 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 标签

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

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

After:

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

注意: 已被废弃,复杂的条件判断可以使用 标签。

Choose 标签

标签下包括至少一个 标签、可选的 标签。
标签内容只有在 condition 为 true 时才会渲染,相当于一个 if 条件判断分支。
标签则相当于最后的 else 分支。
Before:

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

After:

  1. <Choose>
  2. <When condition={ test1 }>
  3. <span>IfBlock1</span>
  4. </When>
  5. <When condition={ test2 }>
  6. <span>IfBlock2</span>
  7. </When>
  8. <Otherwise>
  9. <span>ElseBlock</span>
  10. </Otherwise>
  11. </Choose>

For 标签

标签需要声明 of、each 属性。
of 接收的是可以使用迭代器访问的对象。
each 代表迭代器访问时的当前指向元素。
Before:

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

After:

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

注意: 标签不能作为根元素。

With 标签

标签提供变量传参的功能。
Before:

  1. renderFoo = (foo) => {
  2. return <span>{ foo }</span>;
  3. }
  4. // JSX 中表达式调用
  5. {
  6. this.renderFoo(47)
  7. }

After:

  1. <With foo={ 47 }>
  2. <span>{ foo }</span>
  3. </With>

使用这几种标签优化代码,可以减少 JSX 中存在的显式 JavaScript 表达式,使我们的代码看上去更简洁,但是这些标签封装的能力,在编译时需要转换为等价的 JavaScript 表达式。
注意:具体 babel-plugin-jsx-control-statements 插件的使用见第三篇参考文章;Vue 框架已经通过指令的形式支持 v-if、v-else-if、v-else、v-show、slot 等。

总结

以上我们总结了一些常见的逻辑判断优化技巧。当然,编写高质量可维护的代码,除了逻辑判断优化,还需要有清晰的注释、含义明确的变量命名、合理的代码结构拆分、逻辑分层解耦、以及更高层次的贴合业务的逻辑抽象等等,相信各位在这方面也有自己的一些心得,欢迎一起留言讨论~

参考文献