手把手带你拆解redux中compose函数

452 阅读3分钟

这位同学,你是否看过redux关于compose的源码?redux中仅用3行代码就实现了compose功能。优雅的写法让人着迷,但优雅到过于抽象是否让你望而却步?

const compose = (...fns) => {
  if(fns.length === 0) reutrn arg => arg
  if(fns.length === 1) return fns[0]
  return fns.reduce((curr, current) => (...args) => curr(current(...args)))
}

上述代码是redux中关于compose函数的源码。相信很多人和笔者一样第一眼看过去很苦恼这是什么意思,接下来我们分析一下 这3行实现代码到底做了什么

关于compose函数的作用概括起来就是:接受函数作为参数,并依次执行函数,函数的执行结果会作为下一个函数的入参

compose函数的核心代码就在最后一行:fns.reduce()

复习一下reduce函数的知识:是对每一项进行处理并将函数的return结果作为函数的第一个参数。

那么compose函数就可以被简化成:[fn1, fn2, fn3].reduce()

我们来一步一步的分析reduce的过程:

// 我们在写的清楚一些,每个函数都使用函数表达式来写
[
  (num1 => num1 + 1),
  (num2 => num2 + 2),
  (num3 => num3 + 3)
].reduce((curr, current) => {
  return (num) => curr(current(num))
})

reduce是一个数组函数,所以会执行数组的每一项

step 1:

return (payload) => (
  (num1 => num1 + 1)((num2 => num2 + 2)(payload))
)

PS:为了更好的展示,我对调用的函数进行了换行和加上的括号(以下都是如此~)

在step 1执行时(num1 => num1 +1)就是curr, (num2 => num2 + 2)就是current。我们搞清楚第一步后面就会容易许多。

step2:

思考一下第二次执行reduce函数时 对应的 curr, current都是谁? 🤔

return (payload2) => (
  (payload) => (num1 => num1 + 1)(
    (num2 => num2 + 2)(payload)
  )
)((num3 => num3 + 3)(payload2))

看清楚了吗? 其实就是step1的代入!

在 step 2中(payload) => (num1 => num1 + 1)((num2 => num2 + 2)(payload))就是curr,(num3 => num3 + 3)(payload2)就是current

看到这里已经有小伙伴晕倒了,没关系 我们借用变量来表示:

const step2Curr = (payload) => (num1 => num1 + 1)((num2 => num2 + 2)(payload))

return (payload2) => step2Curr((num3 => num3 + 3)(payload2))

还有点晕?我们在简化一下!

const current = (num3 => num3 + 3)
return (payload2) => step2Curr(current(payload))

豁然开朗了吗?我们又回到了compose函数的本体

因为compose本身就是一个抽象函数嘛😄

我们把compose的抽象(高端)代码还原成具体的业务代码(接地气)就变成了:

[
  (num1 => num1 + 1),
  (num2 => num2 + 2),
  (num3 => num3 + 3),
  (num4 => num4 + 4)
].reduce()

const composer = (payload3) => (
  (payload2) => (
    (payload) => (
      (num1 => num1 + 1)(
        (num2 => num2 + 2)(payload)
      )
    )
  )(
    (num3 => num3 + 3)(payload2)
  )
)(
  (num4 => num4 +4)(payload3)
)

我们在没有compose思想前代码是这样婶儿的:

const composer = fn1(fn2(fn3(fn4(1))))

其实这一步就是具体代码被抽象成工具函数的过程

这里笔者想问大家一个问题,看完compose的解析过程 大家理解compose函数为什么是从右到左的执行顺序了吗?

最后附洋葱模型:

18029972-d9dd5f3d7fb58759.webp

还有一个写法比较low的compose函数(但我觉得这一版更能让人理解compose的含义)

const compose2(...fns) {
  const _this = window
  if(fns.length === 0) return arg => arg
  if(fns.length === 1) return fns[0]

  return function(...args) {
    let finalVal = args
    // 除了可以for从数组末尾倒数开始,也可以事先把数组reverse一下
    for(let i = fns.length - 1; i > -1; i--) {
      const v = Array.isArray(finalVal)? finalVal : [finalVal]
      finalVal = fns[i].apply(_this, v)
    }
    return finalVal
  }
}