JavaScript中的Pipe

JavaScript中的Pipe

本文会介绍Pipe在函数式编程中的基本概念,怎么用Pipe让我们的代码变得更美好,以及新的pipe操作符,Fancy的东西在后面!
什么是Pipe?
先用一个最简单的例子来看一下什么是pipe,现在我们有两个最简单的函数addOne和addTwo,分别对于参数加一和加二:

const addOne = x => x + 1
const addTwo = x => x + 2

现在我们想让一个参数通过第一个函数之后再通过第二个函数,最直接最简单的方法是:

addTwo(addOne(1)) // 4

我们来写一个简单的pipe函数,它返回一个新的函数,来达到我们上面的目的:

const pipe = (func1, func2) => x => func2(func1(x))
 
const addThree = pipe(
  addOne,
  addTwo
)
 
addThree(1) // 4

嗯,现在还看不出来什么好处,但是当我们要经过的Transform越来越多的时候,这样的好处就会越来越明显:

const addTen = pipe(
  addOne,
  addTwo,
  addThree,
  addFour
)

所以我们需要一个更牛逼的pipe函数,它可以接受任意数量的参数,并从第一个开始,依次接受原始数值,输出值传递给下一个函数。等等,我们好像想到了什么,遍历一个数组,把输出值当作下一个的输入,怎么听着都和reduce很像。所以,直接用Array.prototype.reduce就可以写一个简单的pipe函数:

const pipe = ...args => x => 
  args.reduce(
    (outputValue, currentFunction) => currentFunction(outputValue),
    x
  )

 

  • 为什么要用Pipe

Pipe让我们将一些小功能随意的拼凑组合在一起,组成我们自己需要的功能。还记得React中的High Order Component(HOC)吗?越来越多的插件开始让你使用HOC,比如我经常遇到类似下面的代码:

// 没有pipe,function hell...
withRouter(
  withTitle('Awesome title')(
    translate('translations')(
      connect(mapStateToProps, mapDispatchToProps)(
        Container
      )
    )
  )
)
 
// 使用pipe
pipe(
  connect(mapStateToProps, mapDispatchToProps),
  translate('translations'),
  withTitle('Awesome title'),
  withRouter
)(Container)
 
// 使用pipe组成你需要的pattern
const withGeneralContainerProps = pipe(
  connect(mapStateToProps, mapDispatchToProps),
  translate('translations'),
  withTitle('Awesome title'),
  withRouter
)
 
withGeneralContainerProps(Container)
  • Pipe和Prototype

在JavaScript中,经常有通过prototype进行的链式操作,一般来说这种代码如果在项目中看到还是挺喜闻乐见的,感觉代码还是比较干净,比如:

[1, 2, 3, 3, 5]
  .map(i => i * 2)
  .filter(i => i !== 10)
  .reduce((acc, cur) => acc + cur)

但同时,基于prototype的这种链式操作是很难扩展的,比如我们想在上面的代码中间加一个新的操作符uniq,去除重复值:

// 臣妾做不到。。。因为没有Array.prototype.uniq。。。
[1, 2, 3, 3, 5]
  .map(i => i * 2)
  .uniq()
  .filter(i => i !== 10)
  .reduce((acc, cur) => acc + cur)
 
// 有一种办法是在之前的某个地方改变prototype, 但你真的想这么做么?
Array.prototype.uniq = function () {
  return Array.from(new Set(this))
}

不过如果我们把每个小函数拆开来,使用pipe,在保持代码清晰的同时,扩展性得到了巨大的提升,比如下面的写法

import * as R from 'ramda'
 
R.pipe(
  R.map(i => i * 2),
  R.uniq,
  R.filter(i => i !== 10),
  R.reduce(R.add, 0)
)([1, 2, 3, 3, 5])

 

更多关于Ramda可以参考Github或文档。同时要提一下的是,这也是为什么rxjs现在全改成了用pipe,而不使用prototype的原因。

等等,也许你并不需要Pipe?
刚才我们所做的其实都是在用functional programming的方式去写JavaScript,更彻底更优雅的解决方式,可能是在语法上的改变。Proposal-pipeline-operator已经在JavaScript中社区提出来一段时间了,但是具体方案还有分歧,可以去看Github Issue,这里我们就不说太多了,但是就算是基础版本的方案,个人觉得也是非常大的便利,而且如果你在用Babel,今天就可以开始使用。

按照Babel文档添加插件,proposal先用minimal就好,等你弄清楚别的可以再尝试:

{
  "plugins": [["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]]
}

还是上面的例子:

import * as R from 'ramda'
 
[1, 2, 3, 4, 5]
  |> R.map(i => i * 2)
  |> R.uniq
  |> R.filter(i => i !== 10)
  |> R.reduce(R.add, 0)
  // 是的,我们还可以写arrow function,
  |> (_ => 'done, result is ' + _)
  |> console.log

或者是React的例子:

Container
  |> connect(mapStateToProps, mapDispatchToProps)
  |> translate('translations')
  |> withTitle('Awesome title')
  |> withRouter

哇塞,看的真是有点爽,JavaScript什么时候加一下Pattern match...简单说来

a |> b // b(a)

在Elixir,Ocaml等等的函数式语言中其实一直都有,而且社区对于怎么支持async function还有两个不同的提案,感兴趣的同学们都可以去尝试。

虽然只是写法上的不同,但是我相信这个改变的影响还是会比较深远的,语言的不同会慢慢导致大家思考问题的方式有细微的差异,期待这个提案尽快确定下来~

React都用Hook了,还要什么Class !

小编是一个有着5年工作经验的前端开发工程师,关于前端编程,自己有做材料的整合,一个完整的前端编程学习路线,学习材料和工具,+我的威信收取,免费送给tanzhou-10838大家,希望你也能凭着自己的努力,成为下一个优秀的程序员。
————————————————
版权声明:本文为CSDN博主「程序员一木」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tzllxya/article/details/90702581

posted on 2022-11-04 19:25  漫思  阅读(304)  评论(0编辑  收藏  举报

导航