前端常见的 5 种嵌套太深的场景及解决方案

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

fetchData1(data1 =>
  fetchData2(data2 =>
    fetchData3(data3 =>
      fetchData4(data4 =>
        fetchData5(data5 =>
          fetchData6(data6 =>
            fetchData7(data7 =>
              done(data1, data2, data3, dat4, data5, data6, data7)
            )
          )
        )
      )
    )
  )
)
前端常见的 5 种嵌套太深的场景及解决方案

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

场景1: 回调地狱

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

fetchData1(data1 =>
  fetchData2(data2 =>
    fetchData3(data3 =>
      done(data1, data2, data3)
    )
  )
)

解决方案

方案1: Promise

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

let data1, data2, data3
fetchData1()
  .then(data => {
    data1 = data
    return fetchData2()
  })
  .then(data => {
    data2 = data
    return fetchData3()
  })
  .then(data => {
    data3 = data
    done(data1, data2, data3)
  })

改完心情好了很多~

前端常见的 5 种嵌套太深的场景及解决方案

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

function fetchData1 ({
  return new Promise(resolve) {
    ...
    // 异步回来了
    resolve(data)
  }
}

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

Promise.all([
  fetchData1(),
  fetchData2(),
  fetchData3()
]).then(([data1, data2, data3]) => {
  done(data1, data2, data3)
})

方案2: async/await

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

async function fetch({
  const data1 = await fetchData1()
  const data2 = await fetchData2()
  const data3 = await fetchData3()
  done(data1, data2, data3)
}

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

场景2: if 嵌套

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

const isEvenNum = num => Number.isInteger(num) && num % 2 === 0
const isBetween = num => num > 1 && num < 100
const isDivisible = num => num % 3 === 0 && num % 5 ===  0

if (isEvenNum(num)) { // 是偶数
  if(isBetween(num)) { // 1 到 100 之间
    if(isDivisible(num)) { // 能被 3 和 5 整除
        return true
    }
    return false
  }
  return false
}
return false

解决方案

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

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

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

方案2: 用第三方库 Akua

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

const flag = false
new akua()
  .inject(isEvenNum(num), 'isEvenNum', () => {
    console.log('是偶数')
  })
  .inject(isBetween(num), 'isEvenNum->isBetween', () => {
    console.log('1 到 100 之间')
  })
  .inject(isDivisible(num), 'isBetween->isDivisible', () => {
    console.log('能被 3 和 5 整除')
    flag = true
  })
  .parse()

return flag

场景3: 函数调用嵌套

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

toTable( // 第四步: 上桌
  fry( // 第三步: 炒蛋
    handle( // 第二步: 打蛋
      buy(20// 第一步: 买蛋
    )
  )
)

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

解决方案

方案1: 分成多步写

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

let egg = buy(20)
egg = handle(egg)
egg = fry(egg)
egg = toTable(egg)
console.log(egg)

方案2: 封装成函数

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

pipe(20, [
  buy,
  handle,
  fry,
  toTable
]);

function pipe(prev, fnArr{
  if(fnArr.length > 0) {
    const res = fnArr.shift()(prev)
    return pipe(res, fnArr)
  }  
  return prev
}

场景4: React 高阶组件嵌套

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

class Comp extends React.Component {...}

Wrapper5(
  Wrapper4(
    Wrapper3(
      Wrapper2(
        Wrapper1(Comp)
      )
    )
  )
)

解决方案

方案1: 用类装饰器

写法如下:

@Wrapper5
@Wrapper4
@Wrapper3
@Wrapper2
@Wrapper1
class Comp extends React.Component {...}

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

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

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

function Comp ({
  const tool1 = useWrapper1(...)
  const tool2 = useWrapper2(...)
  const tool3 = useWrapper3(...)
  const tool4 = useWrapper4(...)
  const tool5 = useWrapper5(...)
}

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

场景5: React Context 嵌套

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

<PageContext.Provider
  value={...}
>
  <User.Provider
    value={...}
  >

    <Project.Provider
      value={...}
    >

      <ChildComp />
    </Project.Provider>
  </User.Provider>

</PageContext.Provider>

解决方案

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

let content = <ChildComp />

content = (
  <Project.Provider
    value={...}
  >

    {content}
  </Project.Provider>

)

content = (
  <User.Provider
    value={...}
  >

    {content}
  </User.Provider>

)

content = (
  <PageContext.Provider
    value={...}
  >

    {content}
  </PageContext.Provider>

)

总结

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

参考资料

[1]

Promise: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

[2]

async/await: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function

[3]

Akua: https://Github.com/ShanaMaid/akua

[4]

高阶组件(HOC): https://zh-hans.reactjs.org/docs/higher-order-components.html

[5]

@babel/plugin-proposal-decorators: https://babeljs.io/docs/en/babel-plugin-proposal-decorators

[6]

在React项目中使用自定义装饰器: https://segmentfault.com/a/1190000021654387

[7]

自定义Hook: https://zh-hans.reactjs.org/docs/hooks-custom.html


关注公众号:前端GoGoGo,助你升职加薪~

原文始发于微信公众号(前端GoGoGo):前端常见的 5 种嵌套太深的场景及解决方案

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/45626.html

(0)

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!