前端开发中的响应式编程

Definition / 定义

响应式编程是一种面向数据流变化传播编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

**引证解释 **

例如,对于 a=b+c 这个表达式的处理,在命令式编程中,会先计算 b+c 的结果,再把此结果赋值给 变量a,因此 b,c 两值的变化不会对 变量a 产生影响。但在响应式编程中,变量a 的值会随时跟随 b,c 的变化而变化。

Reactive programming is programming with asynchronous data streams.

Data Stream / 数据流

数据流可以看成是时间维度的数组

流的创建

JavaScript 复制代码
import { from, of, fromEvent, interval, fromFetch } from 'rxjs'; 
// 以下返回类型都是observable
// 同步
from([1, 2, 3, 4, 5]); // 1,2,3,4,5
of(1, 2, 3, 4, 5); // 1,2,3,4,5
range(1, 5); // 从1开始,依次递增输出5个数,1,2,3,4,5
from('Hello World'); //'H','e','l','l','o',' ','W','o','r','l','d'
// 异步
fromEvent(document, 'click');
fromEvent(document, 'onmousemove');
from(new Promise(resolve => resolve('Hello World!'))); // 'Hello World!'
interval(1000); // 每秒依次输出0,1,2,3,4,5,6....
fromFetch(`https://api.github.com/search/users`);

流的操作

转换

JavaScript 复制代码
range(1,3)
  .pipe(map((v) => v * 10))
  .subscribe((r) => {
    console.log(r); // 10,20,30
  });

过滤

JavaScript 复制代码
range(0, 5)
  .pipe(filter((x) => x % 2 === 1))
  .subscribe((r) => {
    console.log(r); // 1,3
  });

组合

JavaScript 复制代码
const stream1 = timer(0, 2000).pipe(
  take(3),
  map((v, index) => ["a", "b", "c"][index])
);
const stream2 = timer(1000, 2000).pipe(
  take(3),
  map((v, index) => ["d", "e", "f"][index])
);
merge(stream1, stream2).subscribe((r) => {
  console.log(r); // 每秒依次输出a, d, b, e, c, f
});

Design Pattern / 设计模式

观察者模式

监听数据流的过程就是订阅,接受订阅的对象(即订阅拿到数据后的处理方法)叫做观察者/订阅者 实例:Vue数据双向绑定

发布订阅模式

发布 --- 订阅模式是基于观察者模式进行通用化设计,松散耦合,灵活度更高

订阅者不感知发布者

实例:Vue事件总线 EventBus

Implementation / 落地

Rxjs (ReactiveX.js)

Observable

Observable 是多个值的惰性推送集合。它填补了下面表格中的空白

单个值 多个值
拉取 Function Iterator
推送 Promise (立即执行) Observable (惰性)
  • 什么是拉取? 在拉取体系中,由消费者来决定何时从生产者那里接收数据。生产者本身不知道数据是何时交付到消费者手中的。

  • 什么是推送? 在推送体系中,由生产者来决定何时把数据发送给消费者。消费者本身不知道何时会接收到数据。

JavaScript 复制代码
const observer = {
   next: x => console.log(x),
   error: err => console.error('something wrong occurred: ' + err),
   complete: () => console.log('done'),
}

const observable1 = new Observable<number>(
  subscriber =>{
    subscriber.next(1);
    subscriber.next(2);
    subscriber.complete();
})
const observable2 = interval(1000).pipe(take(5));

observable1.subscribe(observer); // 1, 2, done
observable2.subscribe(observer);// 每秒依次输出0, 1, 2, 3, 4, done

observer -> 观察者
observable -> 可观察对象
subscriber -> 订阅者 == 观察者,一般在库内使用,外部api都用observer
subscribe() -> 订阅方法

Observable部分源码

JavaScript 复制代码
export class Observable<T> implements Subscribable<T> {
  ......
  constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
    if (subscribe) {
      this._subscribe = subscribe;
    }
  }
  subscribe(observer?: Partial<Observer<T>>): Subscription {
    const subscriber = new Subscriber(observer);
    subscriber.add(this._trySubscribe(subscriber));
    return subscriber;
  }
  protected _trySubscribe(sink: Subscriber<T>): TeardownLogic {
    try {
      return this._subscribe(sink);
    } catch (err) {
      sink.error(err);
    }
  }
  ......
}
  • Cold Observable

    • 当cold Observable被observer订阅时,才开始推送数据
    • Cold Observable推送的数据不在订阅者中共享
    • 录播
    • e.g http请求
  • Hot Observable

    • 在订阅开始前就已经是在推送数据的状态了,订阅后收不到之前推送的数据
    • Hot Observable推送的数据在所有订阅者中共享
    • 直播
    • e.g 鼠标移动事件

Subject (Hot Observable)

既是Observable,也是Observer

JavaScript 复制代码
const observer1 = (v) => { console.log('observer1', v); }
const observer2 = (v) => { console.log('observer2', v); }

const subject = new Subject();

// 作为Observable
subject.suscribe(observer1); 
subject.suscribe(observer2); 
subject.next(1);
// observer1和observer2都输出1

// 作为Observer
Interval(1000).subscribe(subject); // observer1和observer2每秒输出1,2,3,4...

普通的Observable并不具备多路推送的能力(每一个Observer都有自己独立的执行环境),而Subject可以共享一个执行环境

子孙后代:BehaviorSubject, ReplaySubject ....

Operators / 操作符

rxmarbles.com/

@formily/reactive

主要是利用es6的Proxy去深度劫持响应式对象(Observable)

JavaScript 复制代码
import { observable, autorun } from '@formily/reactive'

const obs = observable({
  aa: {
    bb: 123,
  },
})

autorun(() => {
  console.log(obs.aa.bb)
})

obs.aa.bb = 321
// 输出:
// 123
// 321

Examples / 举例一些适用场景

单个场景

双击事件

需求:

在某个时间段内(250ms)

  1. 连续点击三次甚至更多,应当被当成一次双击事件

  2. 只点击一次不能被当成双击事件

JavaScript 复制代码
import { buffer, filter, fromEvent, debounceTime } from 'rxjs';
const clicks$ = fromEvent(document, 'click');
const doubleClicks$ = clicks$.pipe(
  buffer(clicks$.pipe(debounceTime(250))),
  map(bufferArr => bufferArr.length),
  filter(len => len >= 2)
);
doubleClicks$.subscribe((r) => {
  console.log('double click');
});

自动补全 / Typehead

当有新的输入时便不再关心之前请求的响应结果

只处理最新的请求的响应,以防旧的请求响应在新的请求响应之后返回

JavaScript 复制代码
fromEvent(document.getElementById('myInput'), 'keyup')
    .pipe(
      map((e: KeyboardEvent) => (e.target as HTMLInputElement).value),
      switchMap((value) => fromFetch(`https://api.github.com/search/users?q=${value}`))
    )
    .subscribe((data) => console.log(data));

swicthMap在同一时间只维护一个内部订阅;它可以取消正在进行的网络请求

switchMap : rxmarbles.com/#switchMap

Http请求重试

需求:

请求失败后需要至少重试2次,如果第一次重试通过,就不进行第二次重试

JavaScript 复制代码
  const request$ = fromFetch(`https://api.github.com/search/users?qa=w`);
  request$.pipe(retry(2)).subscribe((r) => {
    console.log(r);
  });

综合场景

  • IM,即时通讯工具,聊天app

  • 游戏 cocos creator+rxjs

  • ......

总结

总的来说,响应式编程会非常适用于复杂的异步场景/事件驱动的场景,它可以帮助我们创建更娇小、更灵活并且更易于理解的代码,从而使我们更易于处理复杂的异步场景。

RxJS 是响应式编程在 JavaScript 中的实现,它为处理异步和基于事件的程序提供了强大而灵活的工具。

响应式编程在前端开发中有很多应用,如在 Vue 和 Angular 中,都有对响应式编程的应用,用于处理 UI 的不断变化。

Reference

gist.github.com/staltz/868e...

github.com/Reactive-Ex...

cn.rx.js.org/manual/over...

相关推荐
你行你上12 天前
在代码中写死业务计算公式与计算公式模板中选择了与
响应式编程
zhangmeng1 个月前
关于RxSwift中ReplaySubject,你看这个就明白了
ios·响应式编程·rxswift
余生H1 个月前
JS异步编程进阶(二):rxjs与Vue、React、Angular框架集成及跨框架状态管理实现原理
javascript·vue.js·react.js·angular·rxjs·异步编程
萧曵 丶3 个月前
SpringBoot3 响应式编程
springboot·springboot3·响应式编程
王宏亮4 个月前
前端对视频自动抽帧生成预览轴,给一个URL即可
vue.js·响应式编程·动效
OceanSky65 个月前
spring-boot-starter-data-redis是否支持reactive响应式编程
redis·响应式编程·reactive·lettuce
和耳朵6 个月前
响应式编程会给Java带来哪些问题?
java·后端·响应式编程
春哥的魔法书6 个月前
Spring WebFlux:响应式编程
java·spring·响应式编程·webclient·webflux
东皋长歌6 个月前
响应式编程Spring Reactor探索
java·开发语言·spring·reactor·响应式编程
和耳朵7 个月前
这,就是响应式编程!(如何用更少的线程做更多的事)
java·后端·响应式编程