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