> 这是第 <font size=3 color="red">64</font> 篇不掺水的原创,想获取更多原创好文,请搜索公众号关注我们吧~ > 本文首发于政采云前端博客:编写高质量可维护的代码:逻辑判断open in new window

前言

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

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

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

JavaScript 语法篇

嵌套层级优化

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

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

如果提前 return 掉无效条件,将 if else的多重嵌套层次减少到一层,更容易理解和维护。

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

多条件分支的优化处理

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

function pick(color) {
  // 根据颜色选择水果
  if(color === &#39;red&#39;) {
      return [&#39;apple&#39;, &#39;strawberry&#39;]; 
  } else if (color === &#39;yellow&#39;) {
      return [&#39;banana&#39;, &#39;pineapple&#39;];
  } else if (color === &#39;purple&#39;) {
      return [&#39;grape&#39;, &#39;plum&#39;];
  } else {
      return [];
  }
}

在上面的实现中:

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

使用 switch case 优化上面的代码后:

function pick(color) {
  // 根据颜色选择水果
  switch (color) {
    case &#39;red&#39;:
      return [&#39;apple&#39;, &#39;strawberry&#39;];
    case &#39;yellow&#39;:
      return [&#39;banana&#39;, &#39;pineapple&#39;];
    case &#39;purple&#39;:
      return [&#39;grape&#39;, &#39;plum&#39;];
    default:
      return [];
  }
}

switch case 优化之后的代码看上去格式整齐,思路很清晰,但还是很冗长。继续优化:

  • 借助 Object 的 { key: value } 结构,我们可以在 Object 中枚举所有的情况,然后将 key 作为索引,直接通过 Object.key 或者 Object[key] 来获取内容
const fruitColor = {
  red: [&#39;apple&#39;, &#39;strawberry&#39;],
  yellow: [&#39;banana&#39;, &#39;pineapple&#39;],
  purple: [&#39;grape&#39;, &#39;plum&#39;],
};
function pick(color) {
  return fruitColor[color] || [];
}
  • 使用 Map 数据结构,真正的 (key, value) 键值对结构 ;
const fruitColor = new Map()
.set(&#39;red&#39;, [&#39;apple&#39;, &#39;strawberry&#39;])
.set(&#39;yellow&#39;, [&#39;banana&#39;, &#39;pineapple&#39;])
.set(&#39;purple&#39;, [&#39;grape&#39;, &#39;plum&#39;]);

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

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

为了更好的可读性,还可以通过更加语义化的方式定义对象,然后使用 Array.filter 达到同样的效果。

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

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

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

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

多条件判断

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

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

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

试试 Array.includes ~

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

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

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

function match() {
  let isAllRed = true;

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

  console.log(isAllRed); // false
}

上面的实现中,主要是为了处理数组中的所有项都符合条件。

使用 Array.every 可以很容的实现这个逻辑:

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

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

  console.log(isAllRed); // false
}

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

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

如果想知道是否有红色水果,可以直接使用 Array.some 方法:

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

// 条件:是否有红色水果 
const isAnyRed = fruits.some(f =&gt; f.color == &#39;red&#39;);

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

函数默认值

使用默认参数

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

我们经常需要处理函数内部的一些参数默认值,上面的代码大家都不陌生,使用函数的默认参数,可以很好的帮助处理这种场景。

const buyFruit = (fruit,amount = 1) =&gt; {
 if(!fruit){
		return
  }
  console.log(amount,&#39;amount&#39;)
}

我们可以通过 Babel 的转译来看一下默认参数是如何实现的。

从上面的转译结果可以发现,只有参数为 undefined 时才会使用默认参数。

测试的执行结果如下:

buyFruit(&#39;apple&#39;,&#39;&#39;);  // amount
buyFruit(&#39;apple&#39;,null);  //null amount
buyFruit(&#39;apple&#39;);  //1 amount

所以使用默认参数的情况下,我们需要注意的是默认参数 amount=1 并不等同于 amount || 1

使用解构与默认参数

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

Before:

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

After:

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

复杂数据解构

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

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

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

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

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

这种情况下,如果项目中使用 lodash 库,可以使用其中的 lodash/get 方法。

import lodashGet from &#39;lodash/get&#39;;

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

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

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

使用场景:策略模式属于对象行为模式,当遇到具有相同行为接口、行为内部不同逻辑实现的实例对象时,可以采用策略模式;或者是一组对象可以根据需要动态的选择几种行为中的某一种时,也可以采用策略模式;这里以第二种情况作为示例:

Before:

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

enjoy({ type: &#39;juice&#39;, fruits });

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

After:

const TYPE = {
  JUICE: &#39;juice&#39;,
  SALAD: &#39;salad&#39;,
  JAM: &#39;jam&#39;,
};

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

function enjoy({ type = TYPE.JUICE, fruits }) {
  if (!type) {
    console.log(&#39;请直接享用!&#39;);
    return;
  }
  if (!fruits || !fruits.length) {
    console.log(&#39;请先采购水果!&#39;);
    return;
  }
  return strategies[type](fruits);
}

enjoy({ type: &#39;juice&#39;, fruits });

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

JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。一般在 React 中使用 JSX 来描述界面信息,ReactDOM.render() 将 JSX 界面信息渲染到页面上。

在 JSX 中支持 JavaScript 表达式,日常很常见的循环输出子组件、三元表达式判断、再复杂一些直接抽象出一个函数。

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

JSX-Control-Statements

JSX-Control-Statementsopen in new window 是一个 Babel 插件,它扩展了 JSX 的能力,支持以标签的形式处理条件判断、循环。

If 标签

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

Before:

{ condition() ? &#39;Hello World!&#39; : null }   

After:

&lt;If condition={ condition() }&gt;Hello World!&lt;/If&gt;   

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

Choose 标签

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

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

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

Before:

{ test1 ? &lt;span&gt;IfBlock1&lt;/span&gt; : test2 ? &lt;span&gt;IfBlock2&lt;/span&gt; : &lt;span&gt;ElseBlock&lt;/span&gt; }

After:

&lt;Choose&gt;
  &lt;When condition={ test1 }&gt;
    &lt;span&gt;IfBlock1&lt;/span&gt;
  &lt;/When&gt;
  &lt;When condition={ test2 }&gt;
    &lt;span&gt;IfBlock2&lt;/span&gt;
  &lt;/When&gt;
  &lt;Otherwise&gt;
    &lt;span&gt;ElseBlock&lt;/span&gt;
  &lt;/Otherwise&gt;
&lt;/Choose&gt;

For 标签

<For> 标签需要声明 of、each 属性。

of 接收的是可以使用迭代器访问的对象。

each 代表迭代器访问时的当前指向元素。

Before:

{
  (this.props.items || []).map(item =&gt; {
      return &lt;span key={ item.id }&gt;{ item.title }&lt;/span&gt;
  })
}

After:

&lt;For each=&quot;item&quot; of={ this.props.items }&gt;
   &lt;span key={ item.id }&gt;{ item.title }&lt;/span&gt;
&lt;/For&gt;

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

With 标签

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

Before:

renderFoo = (foo) =&gt; {
    return &lt;span&gt;{ foo }&lt;/span&gt;;
}

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

After:

&lt;With foo={ 47 }&gt;
  &lt;span&gt;{ foo }&lt;/span&gt;
&lt;/With&gt;

使用这几种标签优化代码,可以减少 JSX 中存在的显式 JavaScript 表达式,使我们的代码看上去更简洁,但是这些标签封装的能力,在编译时需要转换为等价的 JavaScript 表达式。

注意:具体 babel-plugin-jsx-control-statements 插件的使用见第三篇参考文章;Vue 框架已经通过指令的形式支持 v-if、v-else-if、v-else、v-show、slot 等。

总结

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

参考文献

推荐阅读

分分钟教会你搭建企业级的 npm 私有仓库open in new window

一份值得收藏的 Git 异常处理清单open in new window

招贤纳士

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

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“ 5 年工作时间 3 年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com