嵌套太深的代码可读性差,可维护性差。让人产生“敬畏感”。比如:
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[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)
})
改完心情好了很多~

注意:上面 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>
)
总结
嵌套会导致代码的可读性和维护性很差。要解决嵌套问题,本质上是将嵌套的代码转化为扁平的代码。
参考资料
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