给代码洁癖者的cycleJs教程(一)

493 阅读2分钟
对于洁癖者而言,理想的前端模型无疑应该是:

> app = view(data)

这里有两个问题待解决:

### 数据从哪里来?

在react里,是用hooks绑定到组件上或者放在redux里,在洁癖者看来,hooks就是一块补丁,破坏了结构的优雅;redux则使得程序依赖一块程序外的数据,程序不再是[分形](https://baike.baidu.com/item/%E5%88%86%E5%BD%A2/85449)的。这是更不能接受的,未来的框架无疑应该是分形的。

### 事件怎么处理?

在react里,这些事件处理写在ui里,绑定回调函数去修改数据,这就像更像补丁了。这些绑定函数散落在各处,很难优雅的组织和管理。另外说好的单向数据流呢? 数据从上面流到ui里,又在ui的事件里去修改它,这起码从视觉上看不是“单项”的...

我们来看看cycle的模型是怎样的:

### 流产生数据

在cycle里,事件也是一种特殊的数据,它们是在时间轴上不断吐出的数据,以一个couter程序为例:

const intent = (DOM: MainDOMSource) => xs.merge(  DOM.select('.dec').events('click').mapTo(-1),  DOM.select('.reset').events('click').mapTo(0),  DOM.select('.inc').events('click').mapTo(1),)

可以看出,所有的事件并不会执行某种操作,而是map出一个数据,这是frp相对于传统程序的一个重要的思路转变: **不再有任何的“操作”, 只有数据到数据的映射**,流产生的数据经过汇总,产生出给view用的最终数据:

const model = (intent$: Stream<number>) => intent$  .fold((acc, i) => i ? acc + i : 0, 0)

### 树据产生视图

这一步就很简单了,就是纯粹的渲染了:

const view = (model$: Stream<number>) => model$.map(i =>  div('.f3', [    button('.dec', '-'),    button('.reset', i),    button('.inc', '+'),  ]))


view 层是非常纯净的,只有纯粹的渲染,没有任何事件绑定等任何额外的东西。

这里是完整代码:

import xs, { Stream } from 'xstream'import { run } from '@cycle/run'import { div, button, makeDOMDriver, MainDOMSource } from '@cycle/dom'const intent = (DOM: MainDOMSource) => xs.merge(  DOM.select('.dec').events('click').mapTo(-1),  DOM.select('.reset').events('click').mapTo(0),  DOM.select('.inc').events('click').mapTo(1),)const model = (intent$: Stream<number>) => intent$  .fold((acc, i) => i ? acc + i : 0, 0)const view = (model$: Stream<number>) => model$.map(i =>  div('.f3', [    button('.dec', '-'),    button('.reset', i),    button('.inc', '+'),  ]))run(  ({ DOM }: {DOM: MainDOMSource}) => ({ DOM: view(model(intent(DOM))) }),  { DOM: makeDOMDriver('#root') })

**cycle称这一模式为MDI: intent -> modle -> view**

## 总结

cycle不再有state,不再有事件绑定。事件也是一种树据,事件map出数据,数据map出视图。这也是函数式编程的精髓: **把程序看作是数据到数据的映射**。