嵌套太深的代码可读性差,可维护性差。让人产生“敬畏感”。比如:

  1. fetchData1(data1 =>
  2. fetchData2(data2 =>
  3. fetchData3(data3 =>
  4. fetchData4(data4 =>
  5. fetchData5(data5 =>
  6. fetchData6(data6 =>
  7. fetchData7(data7 =>
  8. done(data1, data2, data3, dat4, data5, data6, data7)
  9. )
  10. )
  11. )
  12. )
  13. )
  14. )
  15. )

big-head.gif

本文介绍 5 种嵌套太深的场景及解决方案。

场景1: 回调地狱

用回调函数的方式来处理多个串行的异步操作,会造成嵌套很深的情况。俗称“回调地狱”。如:

  1. fetchData1(data1 =>
  2. fetchData2(data2 =>
  3. fetchData3(data3 =>
  4. done(data1, data2, data3)
  5. )
  6. )
  7. )

解决方案

方案1: Promise

Promise 可以将串行的异步,处理成链式调用。用 Promise 改写上面的代码如下:

  1. let data1, data2, data3
  2. fetchData1()
  3. .then(data => {
  4. data1 = data
  5. return fetchData2()
  6. })
  7. .then(data => {
  8. data2 = data
  9. return fetchData3()
  10. })
  11. .then(data => {
  12. data3 = data
  13. done(data1, data2, data3)
  14. })

改完心情好了很多~
nice.gif

注意:上面 fetchData1,fetchData2,fetchData3 的返回值的必须都是 Promise 对象。类似:

  1. function fetchData1 () {
  2. return new Promise(resolve) {
  3. ...
  4. // 异步回来了
  5. resolve(data)
  6. }
  7. }

如果这几个异步可以并行操作,可以这么写:

  1. Promise.all([
  2. fetchData1(),
  3. fetchData2(),
  4. fetchData3()
  5. ]).then(([data1, data2, data3]) => {
  6. done(data1, data2, data3)
  7. })

方案2: async/await

async/await 比用 Promise 更优雅。用 async/await 改写上面的代码如下:

  1. async function fetch() {
  2. const data1 = await fetchData1()
  3. const data2 = await fetchData2()
  4. const data3 = await fetchData3()
  5. done(data1, data2, data3)
  6. }

注意:上面 fetchData1,fetchData2,fetchData3 的返回值也必须都是 Promise 对象。同时,用 await 的函数,必须在函数名前加 async。

场景2: if 嵌套

在条件语句中,如果判断条件很多,会出现嵌套很深或判断条件很长的情况。比如,判断一个值是否是: 1 到 100 之间,能被 3 和 5 整除的偶数。这么写:

  1. const isEvenNum = num => Number.isInteger(num) && num % 2 === 0
  2. const isBetween = num => num > 1 && num < 100
  3. const isDivisible = num => num % 3 === 0 && num % 5 === 0
  4. if (isEvenNum(num)) { // 是偶数
  5. if(isBetween(num)) { // 1 到 100 之间
  6. if(isDivisible(num)) { // 能被 3 和 5 整除
  7. return true
  8. }
  9. return false
  10. }
  11. return false
  12. }
  13. return false

解决方案

方案1: 将判断条件结果放在数组中

将判断条件结果放在数组中,可以将嵌套的条件判断,改成扁平的遍历数组值的判断。代码实现如下:

  1. return [isEvenNum(num), isBetween(num), isDivisible(num)]
  2. .every(flag => flag)

方案2: 用第三方库 Akua

Akua 可以将条件嵌套转化成链式调用。代码实现如下:

  1. const flag = false
  2. new akua()
  3. .inject(isEvenNum(num), 'isEvenNum', () => {
  4. console.log('是偶数')
  5. })
  6. .inject(isBetween(num), 'isEvenNum->isBetween', () => {
  7. console.log('1 到 100 之间')
  8. })
  9. .inject(isDivisible(num), 'isBetween->isDivisible', () => {
  10. console.log('能被 3 和 5 整除')
  11. flag = true
  12. })
  13. .parse()
  14. return flag

场景3: 函数调用嵌套

执行多个函数调用,每个函数输出是下个函数的输入,会造成很深的嵌套。如:

  1. toTable( // 第四步: 上桌
  2. fry( // 第三步: 炒蛋
  3. handle( // 第二步: 打蛋
  4. buy(20) // 第一步: 买蛋
  5. )
  6. )
  7. )

上面的代码模拟的是炒蛋的过程:买蛋 -> 打蛋 -> 炒蛋 -> 上桌。

解决方案

方案1: 分成多步写

分成多步写,用临时变量接收上个函数的调用结果。实现代码如下:

  1. let egg = buy(20)
  2. egg = handle(egg)
  3. egg = fry(egg)
  4. egg = toTable(egg)
  5. console.log(egg)

方案2: 封装成函数

用递归的方式,把上一个函数的执行结果,传递到下一个函数。代码如下:

  1. pipe(20, [
  2. buy,
  3. handle,
  4. fry,
  5. toTable
  6. ]);
  7. function pipe(prev, fnArr) {
  8. if(fnArr.length > 0) {
  9. const res = fnArr.shift()(prev)
  10. return pipe(res, fnArr)
  11. }
  12. return prev
  13. }

场景4: React 高阶组件嵌套

在 React 写的应用中,会出现一个组件被很多个高阶组件(HOC)包裹,造成嵌套很深的情况。如:

  1. class Comp extends React.Component {...}
  2. Wrapper5(
  3. Wrapper4(
  4. Wrapper3(
  5. Wrapper2(
  6. Wrapper1(Comp)
  7. )
  8. )
  9. )
  10. )

解决方案

方案1: 用类装饰器

写法如下:

  1. @Wrapper5
  2. @Wrapper4
  3. @Wrapper3
  4. @Wrapper2
  5. @Wrapper1
  6. class Comp extends React.Component {...}

注意:在项目中需要安装些依赖才能使用装饰器。如果是 Webpack 项目,要安装 @babel/plugin-proposal-decorators。具体见: 在React项目中使用自定义装饰器

方案2: 将高阶组件改成自定义Hook

组件中用自定义Hook 是扁平的结构,不存在嵌套。将类组件改成函数组件,将高阶组件改成自定义Hook 可以解决嵌套的问题。写法如下:

  1. function Comp () {
  2. const tool1 = useWrapper1(...)
  3. const tool2 = useWrapper2(...)
  4. const tool3 = useWrapper3(...)
  5. const tool4 = useWrapper4(...)
  6. const tool5 = useWrapper5(...)
  7. }

这个方案对原有代码的改动很大,仅做参考。

场景5: React Context 嵌套

在 React 写的应用中,可以用 Context 来管理子组件间的数据共享。如果共享数据很多,而且类型不同,容易造成顶部组件 Context 嵌套很深。如:

  1. <PageContext.Provider
  2. value={...}
  3. >
  4. <User.Provider
  5. value={...}
  6. >
  7. <Project.Provider
  8. value={...}
  9. >
  10. <ChildComp />
  11. </Project.Provider>
  12. </User.Provider>
  13. </PageContext.Provider>

解决方案

可以用分成多步写的方式解决上面的嵌套。实现代码:

  1. let content = <ChildComp />
  2. content = (
  3. <Project.Provider
  4. value={...}
  5. >
  6. {content}
  7. </Project.Provider>
  8. )
  9. content = (
  10. <User.Provider
  11. value={...}
  12. >
  13. {content}
  14. </User.Provider>
  15. )
  16. content = (
  17. <PageContext.Provider
  18. value={...}
  19. >
  20. {content}
  21. </PageContext.Provider>
  22. )

总结

嵌套会导致代码的可读性和维护性很差。要解决嵌套问题,本质上是将嵌套的代码转化为扁平的代码。