React more Reactive

Reactive 就是响应式,在如今已经算是个老概念了。为何说 more reactive 呢,其实本文最终的主旨仍是要给尚未开始接触 Hooks 或者对于 Hooks 不是那么感冒的同窗安利一下。Hooks 不光是一组 API,他背后承载的是 React 团队想要宣导的一套编程理念。这其中的一部分,就是咱们今天的主角——响应式。咱们今天就来看看,响应式给咱们带来了什么,以及 hooks 和他有什么关系。
javascript

1、先谈主要矛盾

在开始讲正题以前,仍是先为讨论定下一个主旨。React 是开发 Web 应用用的一种框架,开发 Web GUI 是咱们的一个讨论前提。当前的大背景下,咱们的前端开发工做中,主要矛盾之一就是 不断增加的应用复杂度应用开发成本 之间的矛盾。人性就是想付出更少获得更多,复杂度的增加是咱们没法去改变的,咱们只能在成本上作更多的文章,这其中,让一份程序更好写也更好懂就是一个很常见的作法。这也是咱们今天的主角——响应式编程带给咱们的东西。
前端

2、什么是响应式编程

首先咱们能够先看看学术派的 wikipedia 对于响应式有着什么样的解释:





翻译过来就是 响应式编程是一种关注于数据流与变动的传播的声明式编程范式。 从这段描述中咱们须要抓取几个关键词,【声明式编程范式】、【数据流】、【变动传播】。


声明式编程范式是其核心思想,与其对应的能够是命令式的。声明式更像是咱们在数学课上学习的那些公式,是声明以后就是一直有效的。上面文字对 a:=b+c 这一表达在两种范式思惟下的解释其实很好地表现了这一点。这种范式的好处是在于,咱们可以在其指导下,更清晰得去梳理咱们的逻辑,且更容易抽象拆分与组合。整个过程很像总结公式定理,公式的组合推论又造成新的公式。每一个公式声明均可以很简单,简单的公式每每是稳定的,且易懂的,例如“两点之间直线最短”或者“E=mc^2”等等。


数据流则是该范式中重点关注的对象。响应式编程其描述的主体就是数据流。这种描述以数据做为串联,弱化了声明的先后顺序的影响。



变动传播则是实现数据流的核心机制。通常这种机制能够分为 Pull 和 Push,Pull 是下游主动去获取数据,Push 则是变化源主动触发依赖方的更新。在响应式的实现中,可能 Push 的形式会要更常见一些。方法有不少,经常使用的是发布订阅与监听者模式,不少响应式编程的实现库,也主要是基于这个方向去打造的。背后的思想其实就是为了完成声明式而实现的控制反转。java




下面举个不恰当的例子,来讲明一下简单的响应式编程。在电子系统逻辑设计中,咱们常以门电路图来描述,用一样的方式,咱们也能够画出这样的一个代码描述。A 和 B 能够想象成任何上游操做器,对接整个电路的输入端。实现上咱们暂时用 React Hooks 来表达。这里的实现方式可能有点极端,不要在乎哈。


电路图:                                                       
     


代码描述逻辑:react

function Diag({ A, B }) {
  const U4 = useMemo(() => !B, [B]);
  const U5 = useMemo(() => !A, [A]);
  const U1 = useMemo(() => A && U4, [A, U4]);
  const U2 = useMemo(() => B && U5, [B, U5]);
  const X = useMemo(() => U1 && U2, [U1, U2]);
  return X;
}


能够看到咱们每一行都很简单,而且由于是声明式的,先后执行顺序并没关系。最后会造成从 A,B 到 X 的数据流。代码比较简单,你们应该一看就会,体会一下便可。


在 Javascript 讨论圈中,对于响应式编程也有另一种解释方式,说他是一种关注异步数据流处理的编程范式,这里的异步数据流也能够是咱们前端常提到的事件这个概念。我我的理解就是对一系列不肯定什么时间发生的事情,预先提供处理方式。在这种思路的指导下,咱们能够将应用中一切的行为、变动都以数据流的方式作描述,特别是异步的那些。而后咱们可使用一套针对数据流的操做符集合去组合逻辑。能够发现这种解释实际上是和上面总结的是相通的。最终仍是回到了数据流、变动传播、声明式范式的三件套。
编程

3、为何这么作更好

道理我都懂,可是这种作法究竟好在什么地方呢?下面咱们从五个角度去分析一下。
redux

1. 易读性更强,下降理解成本

都说代码是写给机器去运行的,同时也是写给人看的,人看不懂的代码不是好代码。的确,排除竞赛等特殊场景,代码的易读性、可维护性是其在工业生产中很重要的质量指标之一,特别是在前端开发中,甚至能够排上首位。那为何就说这样写易读性就更好了呢,咱们先很少说,先拿一些简单的例子你们感觉一下。框架

// 命令式的
let items = [];
items.push(1);
items.push(2);
items.push(3);
items.push(4);

const newItems = items
  .filter(item => item % 2 === 0)
  .map(item => item + 0.5);

newItems.forEach(item =>
  console.log(item)
);
// Output: 2.5
// Output: 4.5

items.push(5);
items.push(6);
items.push(7);
items.push(8);

// Call again
// 响应式的
import Rx from 'rxjs/Rx';

let items = new Rx.Subject();
items.onNext(1);
items.onNext(2);
items.onNext(3);
items.onNext(4);

const newItems = items
  .filter(item => item % 2 === 0)
  .map(item => item + 0.5);

newItems.forEach(item =>
  console.log(item)
);

items.onNext(5);
items.onNext(6);
// Output: 6.5
items.onNext(7);
items.onNext(8);
// Output: 8.5

实现的功能很简单,就很少作解释了。


总结一下,命令式的程序,就是你告诉计算机你每一步应该怎么作,而响应式则是你告诉计算机,当发生了什么事情以后应该怎么作。听上去是否是就是命令式的更为保姆式一点呢,这种表达形式也与人平常的交流习惯不太同样。并且越细节的行为,可读性相对就越差的。在响应式的写法下,数据的生产与处理是能够剥离的,每一块的代码能够有本身专注的关注点。这也得益于响应式他的执行顺序弱依赖的特性。
异步

2. 更符合交互类应用开发直觉

现代前端工程框架中,无一例外得都采用了一种简单高效的方式去描述总体逻辑,那就是 UI=f(state)。这一样也构成了整个思想中最重要的根基定理之一,界面只与应用状态直接发生关系。数据流就是对应用状态更为具体的声明描述。长期以来,UI 开发方式的演化证实了,UI 开发本质上就是声明式的,由于 UI 上其数据变化的触发源是多样化的,数据流向是复杂可组合的,数据变化时机是无规律的,所以经过声明式的表达方式能够更简洁明了地表达,而且行为的可预期性能够更强。毕竟优秀的程序须要让人看懂。从而能同时下降开发与维护的成本。



svg

3. 依赖静态上下文,而非运行时上下文

在响应式编程中,咱们更少得会去依赖运行时的上下文,由于咱们的数据关系是在书写声明时就肯定的,因此其依赖的是静态上下文。在 JS 中,词法分析阶段就会确认惟一的做用域,这个就是上面说到的静态上下文。


那这其中有哪些好处呢?第一就是咱们在阅读代码的时候就能根据上下文内容确认总体逻辑,减小熟悉逻辑和排查问题的时间成本。第二是咱们在编译阶段就可以进行静态代码分析,去保障咱们的数据流是不是正确的。第三是咱们在书写响应式的具体逻辑时,更习惯用纯函数去描述,这样在测试时,也就更方便咱们去细分测试,而不依赖于运行时的一些总体信息。
模块化

4. 与具体实现解耦

由于响应式编程是一种声明式编程范式,你甚至可使用伪代码进行编写,或者使用图来描述
image.pngimage.png
咱们并不须要特别关心其具体实现细节,就可以保证总体逻辑的正确性。在切换技术方案,或者自己技术升级时都能尽可能少的引入迁移成本。尽可能保持一致的设计理念。举个例子,RxJava、RxJs 等,你能发现多种语言的实现版本。

5. 逻辑的可组合性

现代工业崛起的核心是规模化生产,而规模化生产的基石我认为是标准统一与模块化细分。响应式编程的顺序弱关联,依赖串联等特性使逻辑的自由组装更易实现。而且由于有了一致的编程范式,不一样目的的逻辑模块的组合成本也会更低。

4、use Hooks, thinking in Reactive

很幸运,咱们在使用 React 框架,React 自己就是基于响应式理念开发出来的。不过本来生命周期函数的写法,多多少少有点其余编程方式的影子,有点命令式一点,不够 Reactive。随着 Hooks 的加入,大大增强了 React 开发过程当中响应式的短板。


Hooks 中我认为有一个很是重要的概念,就是 deps,依赖。他改变了咱们原先在书写组件时更关注在什么时机执行什么的思路。让咱们更关注数据自己的变化,真正经过数据去驱动应用的行为。这背后的重点是他改变了咱们编程的思考方式,让咱们从深刻理解 React 在执行的哪一个时间点会作什么事情、调用咱们的哪一个生命周期函数这种事情中解放出来(毕竟有时候生命周期还会调整)。咱们只须要关注,在当前的组件上下文中(这个在框架体系内逃不开,并且我以为 functional 的书写方式自然解决了一部分 immutable 的问题也未必是坏事)中,数据的变化会如何传递,会触发哪些反作用就能够了。代码也会变得更为简洁。对比一下下:

// Classical React Component
class Chart extends Component {
  state = {
    data: null,
  }
  componentDidMount() {
    const newData = getDataWithinRange(this.props.dateRange)
    this.setState({data: newData})
  }
  componentDidUpdate(prevProps) {
    if (prevProps.dateRange != this.props.dateRange) {
      const newData = getDataWithinRange(this.props.dateRange)
      this.setState({data: newData})
    }
  }
  render() {
    return (
      <svg className="Chart" />
    )
  }
}
// Component with hooks
const Chart = ({ dateRange }) => {
  const [data, setData] = useState()
  useEffect(() => {
    const newData = getDataWithinRange(dateRange)
    setData(newData)
  }, [dateRange])
  return (
    <svg className="Chart" />
  )
}


第二个我以为颇有意思的地方是 Hooks 与组件的实例不在书写层面上作强关联,所以逻辑能够独立存在,促进了逻辑与视图的进一步分离,再也不须要使用过去相对笨重的万物皆组件的思路去拆分代码。这背后还有一个奇特的隐式上下文,我认为他对响应式编程最大的做用,在于可以将多个不相关的做用域作叠加。


目前 Hooks 中提供的功能都还比较基础。还较难知足复杂的数据流声明。之前经常使用的 redux + saga 的实现则更像是一个事件模式,其本质可能只算是响应式中的“触发”环节。有一个叫作 redux-observable 的库是将 redux 的 action 模型做为触发器,将其与 Rxjs 的数据流描述能力组合在一块儿,可以使用更加复杂的数据流声明方式,是一个不错的尝试。与 Hooks 结合就能造成一个闭环。

5、小结一下

本文并非想说 Hooks 是万能的。只是在一个启发下,认为响应式编程也许更适合前端开发的模式。对于一部分 OOP 的鼓吹者,我认为应该反思一下在咱们编写前端 UI 时,是否真的用到了 OOP 的多种特性。例如,继承、多态等,JS 自己就难以支持真正的多态,其继承能力在实际使用中也渐渐少用了,毕竟在 UI 领域,组合大于继承仍是比较主流的。OOP 更适合用于抽象复杂的数据模型,不过这个就不在此次的讨论范围内了,咱们能够下次再水一篇文章,名字能够暂定《面向对象是否是已通过气了》。


最后点一下题,要想代码漂亮,逻辑清晰。响应式编程理念值得你一试。Make your React App more Reactive. Thanks for reading :)

文章可随意转载,但请保留此原文连接。
很是欢迎有激情的你加入 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com
相关文章
相关标签/搜索