嵌套太深的代码可读性差,可维护性差。让人产生“敬畏感”。比如:
fetchData1(data1 =>fetchData2(data2 =>fetchData3(data3 =>fetchData4(data4 =>fetchData5(data5 =>fetchData6(data6 =>fetchData7(data7 =>done(data1, data2, data3, dat4, data5, data6, data7))))))))

本文介绍 5 种嵌套太深的场景及解决方案。
场景1: 回调地狱
用回调函数的方式来处理多个串行的异步操作,会造成嵌套很深的情况。俗称“回调地狱”。如:
fetchData1(data1 =>fetchData2(data2 =>fetchData3(data3 =>done(data1, data2, data3))))
解决方案
方案1: Promise
用 Promise 可以将串行的异步,处理成链式调用。用 Promise 改写上面的代码如下:
let data1, data2, data3fetchData1().then(data => {data1 = datareturn fetchData2()}).then(data => {data2 = datareturn fetchData3()}).then(data => {data3 = datadone(data1, data2, data3)})
改完心情好了很多~
注意:上面 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 比用 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 === 0const isBetween = num => num > 1 && num < 100const isDivisible = num => num % 3 === 0 && num % 5 === 0if (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 可以将条件嵌套转化成链式调用。代码实现如下:
const flag = falsenew 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)包裹,造成嵌套很深的情况。如:
class Comp extends React.Component {...}Wrapper5(Wrapper4(Wrapper3(Wrapper2(Wrapper1(Comp)))))
解决方案
方案1: 用类装饰器
写法如下:
@Wrapper5@Wrapper4@Wrapper3@Wrapper2@Wrapper1class Comp extends React.Component {...}
注意:在项目中需要安装些依赖才能使用装饰器。如果是 Webpack 项目,要安装 @babel/plugin-proposal-decorators。具体见: 在React项目中使用自定义装饰器。
方案2: 将高阶组件改成自定义Hook
组件中用自定义Hook 是扁平的结构,不存在嵌套。将类组件改成函数组件,将高阶组件改成自定义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.Providervalue={...}><User.Providervalue={...}><Project.Providervalue={...}><ChildComp /></Project.Provider></User.Provider></PageContext.Provider>
解决方案
可以用分成多步写的方式解决上面的嵌套。实现代码:
let content = <ChildComp />content = (<Project.Providervalue={...}>{content}</Project.Provider>)content = (<User.Providervalue={...}>{content}</User.Provider>)content = (<PageContext.Providervalue={...}>{content}</PageContext.Provider>)
总结
嵌套会导致代码的可读性和维护性很差。要解决嵌套问题,本质上是将嵌套的代码转化为扁平的代码。
