Code that is too deeply nested is less readable and less maintainable. It creates a sense of awe. Such as:

fetchData1(data1= >
  fetchData2(data2= >
    fetchData3(data3= >
      fetchData4(data4= >
        fetchData5(data5= >
          fetchData6(data6= >
            fetchData7(data7= >
              done(data1, data2, data3, dat4, data5, data6, data7)
            )
          )
        )
      )
    )
  )
)
Copy the code

This article describes five scenarios that are too deeply nested and their solutions.

Scenario 1: Callback to Hell

Using callbacks to handle multiple serial asynchronous operations can result in deep nesting. Commonly known as “callback hell.” Such as:

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

The solution

Plan 1: Promise

Promise allows you to make serial asynchronous calls into chained calls. Rewrite the above code with Promise as follows:

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)
  })
Copy the code

Changed the mood a lot better ~

Note: the above fetchData1 fetchData2, fetchData3 return values must be Promise object. Similar to:

function fetchData1 () {
  return new Promise(resolve){...// Asynchrony is back
    resolve(data)
  }
}
Copy the code

If these asynchrony operations can be done in parallel, write:

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

Solution 2: the async/await

Async /await is more elegant than using Promise. Rewrite the above code with async/await as follows:

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

Note: the above fetchData1 fetchData2, fetchData3 return values must also Promise object. Also, to use await functions, async must be preceded by the function name.

Scenario 2: If nesting

In conditional statements, if there are many criteria, deep nesting or long criteria may occur. For example, determine whether a value between 1 and 100 is an even number divisible by 3 and 5. So to write:

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)) { / / is even
  if(isBetween(num)) { // Between 1 and 100
    if(isDivisible(num)) { // Divisible by 3 and 5
        return true
    }
    return false
  }
  return false
}
return false
Copy the code

The solution

Option 1: Put the result of the judgment condition in an array

By placing the result of the criteria in an array, you can change the nested criteria to a flat set of traversal values. The code implementation is as follows:

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

Option 2: use the third-party library Akua

Akua can transform conditional nesting into chained calls. The code implementation is as follows:

const flag = false
new akua()
  .inject(isEvenNum(num), 'isEvenNum'.() = > {
    console.log(It's even.)
  })
  .inject(isBetween(num), 'isEvenNum->isBetween'.() = > {
    console.log('Between 1 and 100')
  })
  .inject(isDivisible(num), 'isBetween->isDivisible'.() = > {
    console.log('Divisible by 3 and 5.')
    flag = true
  })
  .parse()

return flag
Copy the code

Scenario 3: Nested function calls

Making multiple function calls, each function whose output is the input to the next function, can create deep nesting. Such as:

toTable( // Step 4: Serve
  fry( // Step 3: Scrambled eggs
    handle( // Step 2: Beat the eggs
      buy(20) // Step 1: Buy eggs)))Copy the code

The code above simulates the process of scrambled eggs: buy eggs -> beat eggs -> scrambled eggs -> serve.

The solution

Plan 1: Write in multiple steps

Write into multiple steps, using temporary variables to receive the last function call results. The implementation code is as follows:

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

Scheme 2: Encapsulate as a function

You recursively pass the results of the previous function to the next function. The code is as follows:

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
}
Copy the code

Scenario 4: React High-order component nesting

In applications written by React, a component may be wrapped in multiple high-order components (HOC), resulting in deep nesting. Such as:

class Comp extends React.Component {... } Wrapper5( Wrapper4( Wrapper3( Wrapper2( Wrapper1(Comp) ) ) ) )Copy the code

The solution

Option 1: Use class decorators

It is written as follows:

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

Note: Some dependencies need to be installed in the project to use the decorator. For a Webpack project, install @babel/plugin-proposal-decorators. See: Using custom decorators in the React project.

Option 2: Change higher-order components into custom hooks

Components with custom hooks are flat structures with no nesting. Changing class components to function components and higher-order components to custom hooks can solve the nesting problem. It is written as follows:

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

This program to the original code of the change is very big, just for reference.

Scenario 5: React Context nesting

In React applications, Context can be used to manage data sharing between subcomponents. If there is a lot of shared data and the types are different, it is easy to create a very deep nesting of the top component Context. Such as:

<PageContext.Provider value={... } ><User.Provider
    value={... }
  >
    <Project.Provider
      value={... }
    >
      <ChildComp />
    </Project.Provider>
  </User.Provider>
</PageContext.Provider>
Copy the code

The solution

The above nesting can be solved by writing in multiple steps. Implementation code:

let content = <ChildComp />

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

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

content = (
  <PageContext.Provider
    value={... }
  >
    {content}
  </PageContext.Provider>
)
Copy the code

conclusion

Nesting leads to poor readability and maintainability of code. To solve the nesting problem, you essentially convert nested code into flat code.