xReact 函数式范特西

xReact 2.3.0 后加入了一个新功能,那就是一个新的类型 FantasyX。这个类型,会彻底改变编写前端控件的方式。

如果接触过 PureScript 的同学,估计已经对一个叫 Flare的库有所了解,FantasyX 正是 Flare 在 xReact 的实现,但是不同的是,xReact 并没有把 UI 和逻辑揉到一起定义。

只需一个例子

想象一下我们要实现一个函数来做乘法操作,如果不涉及 UI,只是输入两个数,输出结果,会是多么简单的一件事情:

// Number -> Number -> Number
function mult(a, b) {
  return a * b
}
mult(1, 2)

但是如果我们要写成 UI 控件, 将会变得多么复杂:

  • 两个输入控件 <input/>
  • 一个输出控件用来显示结果
  • 绑定函数到 input 的 onChange 上,一旦任何一个 input 发生变化, 则回调函数
  • 还要从函数获取两个 input 的值作为输入

天哪, 为什么前端程序员每天都要干这么蠢的事情. 就算使用 React 来做组件,这些过程也一样不能少.

所以才有了 FantasyX, 他帮助你构造一起 UI 组件的 Monad, Applicative, Functor, Monoid, 如果你没听过这些词, 没关系,例子会解释一切.

还是实现乘法, 非常简单,只需要把普通函数 lift 起来:

// FantasyX -> FantasyX -> FantasyX
let xMult = lift2(mult)

mult 有两个参数, 所以我们需要 lift2, 得到的 xMult 依然是函数, 但是 你会发现他的类型从 接收 Number 并返回 Number 的函数,变成了接收 FantasyX 返回 FantasyX 的函数. 这种操作就叫做 lift

有了这个函数, 我们需要找输入了, mult 需要两个为 Number 的输入, 那么我们的 FantasyX 类型的输入怎么给呢?

let XMult = xMult(xinput('a'), xinput('b'))

擦,不能这么简单吧. xinput 又是什么鬼?

你管,反正这里的 xinput 帮你构造了一个 FantasyX, 在里面编制着炫酷的数据流.

可能是这样的

或者是这样的

自己看源码吧. 不过看之前可能你会需要了解 State Monad

要知道, 到此为止我们得到的 XMult 依然是 FantasyX 类型. 先要得到一个正常的 React Component, 只需要 apply 到一个 React Component 上.

一个简单 Stateless Component

const View = props => (
  <div>
    <input name="a" onChange={props.actions.fromEvent} defaultValue={props.a}/>
    <input name="b" onChange={props.actions.fromEvent} defaultValue={props.b}/>
    <div>{props.output}</div>
  </div>
)
View.defaultProps = { a: "", b: "",output:""}

apply 到 View 上获得 React Component 一枚

let Mult = XMult.apply(View)

完整代码: https://www.webpackbin.com/bins/-KoGxSJ-3pOi4DicUvaq

Functor

想象一下以前构造出来的 ReactComponent, 一旦构造出来就不好修改或重用, 比如还是前面的例子, 你有一个 Mult 控件, 做的是乘法.

那么如果我需要一个新的控件, 而且控件的也需要 a * b, 但是不同的是, 我们新控件的公式是 (a * b)^2. 要是修改函数我们思路很清晰, 构造个新函数呗:

function mmp(a, b) {
  return mult(mult(a, b), mult(a, b))
}

如果是 React, 那就构造个 HoC 把 Mult 控件套进去呗.

但是不管是包函数还是包 React 控件, 都不是一个可持续组合的方式, 而且构造新的 React Class 又会需要很多啰嗦的模板代码, 如果你经常写 React 你知道我在说什么.

Functor 可以免去这些复杂的过程, 而且提供了高可重用与组合的可能.

接着 XMult, 我们很容易能将其变换成 XMMP

let XMMP = XMult.map((s) => ({output: s.output * s.output}))

这样我们就轻松从 XMult 生成一个新的 FantasyX, 在之前的基础上平方一下.

这就像操作数据一样简单 不是吗

[1,2,3].map(x=>x*x)
// [2,4,6]

完整代码: https://www.webpackbin.com/bins/-Kss6m5ORK74CObhAPqB

Monoid

有了变换, 我们可能还需要合并两个 FantasyX, 比如上面的 XMMP 和 XMult, 如果我需要一个同时能具有两种行为的 FantasyX

let XCOMBINE = XMMP.concat(XMult)

仅此, 我们就得到了一个同时具有 XMMP 与 XMult 行为的 FantasyX

当然, 因为他们都会修改 state.output, 合并到一起会导致冲突, 我们稍微修改下 XMMP

let XMMP = XMult.map((s) => ({output2: s.output * s.output}))

这样 View 就可以把 XMMP 和 Mult 的输出都显示出来

完整代码: https://www.webpackbin.com/bins/-Kss6m5ORK74CObhAPqB

Summary

使用 FantasyX, 我们很简单将逻辑从 UI 中分离

  • 写一个一般的简单函数, lift 之, 应用到 xinput 或者其他任何 FantasyX 类型
  • apply 到 View 上, 得到正常 ReactComponent

不但如此, 你还可以简单的变换或者合并 FantasyX

  • FantasyX 实现 Functor, 让你可以 从一个 FantasyX 轻松 map 到另一个 FantasyX
  • FantasyX 实现 Monoid, 简单的 concat 两个 FantasyX, 合成一个 FantasyX, 就跟数组 concat 一样.

更多…