作者:大力智能技术团队-前端 豆花饲养员
写在最前面:
(1) 刚入行时有过一年半Angular的开发经验,当时是做数据看板平台,需要处理看板和图表之间的复杂状态管理,而Angular内置的RxJS数据流管理配合依赖注入的强大特性完美cover住复杂数据流。
(2) 在当下React技术栈开发过程中也使用到RxJS,主要用来做活动流程设计、基础组件数据流管理、跨层级组件通信等,使用数据驱动解耦逻辑。
命令式编程:通过语句或命令修改程序状态。(eg:JS、Java、Python)
函数式编程:函数是第一等公民,通过函数调用、组合等完成复杂操作。(eg:科里化、函数组合)
声明式编程:描述程序逻辑而非控制流。(eg:SQL、HTML、CSS)
响应式编程:基于数据流及其变化传播的声明性编程范式。(eg:ReactUI = f(state)
)
Rx (Reactive Extensions):响应式扩展 RxJS(Reactive Extensions for JavaScript):JS的响应式扩展
Think of RxJS as Lodash for events.
将同步/异步操作统一抽象到流,关注数据流的变更,提供对数据流的一系列操作和处理方法。通过数据流驱动,有效降低代码逻辑耦合。
In computer science, a stream is a sequence of data elements made available over time.
A stream can be thought of as items on a conveyor belt being processed one at a time rather than in large batches.
流是一系列随时间到达的数据。例如:事件流、直播数据流、文本编辑流、WebSocket。
流可抹平同步和异步差异,异步和同步数据都可放入流中。
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
✅优点
❌缺点
Observables are lazy Push collections of multiple values.
Observables是多个值的懒推送集合。
Pull: 数据消费者主动获取数据,数据生产方无感知。
Push:数据生产方决定发送数据给消费者的时机,数据消费者无感知。
单值 | 多值 | |
---|---|---|
Pull | Function | Iterator |
Push | Promise | Observable |
Function
调用时同步返回单个值;Iterator
迭代式调用,调用时返回多个值;Promise
异步返回单个值;Observable
同步/异步地返回多个值。An Observer is a consumer of values delivered by an Observable.
Observer是Observable的数据消费者。
Observer就是普通对象,类型定义如下:
interface Observer<T> {
/** 是否已关闭 */
closed?: boolean;
/** 流发出值时执行 */
next: (value: T) => void;
/** 流出错时执行 */
error: (err: any) => void;
/** 流完成时执行 */
complete: () => void;
}
// 订阅流
// observable.subscribe(observer);
A Subscription is an object that represents a disposable resource, usually the execution of an Observable.
订阅关系代表一个可关闭的资源(通常是Observable的执行)。
// 订阅流
const subscription = stream$.subscribe({
next: (val) => console.info,
error: console.error,
complete: () => console.info('completed'),
});
// 清理订阅
subscription.unsubscribe();
❗️❗️❗️订阅完成后必须调用unsubscribe()
以释放资源,防止内存泄露。
A Subject is like an Observable, but can multicast to many Observers. Subjects are like EventEmitters: they maintain a registry of many listeners.
Subject是一种支持多播的Observable。
next(v)
、error(e)
、complete()
方法,将被广播至所有注册的Observer。Subject类型 | 特点 | 示意图 |
---|---|---|
Subject | * 普通Subject * 支持多播 | |
BehaviorSubject | * 随时间变化的数据流 * 缓存当前值 * 被订阅时会立即推送最新值 | |
ReplaySubject | * 缓存最近的若干值 * 可设置缓存窗口 | |
AsyncSubject | * 仅当完成时发出值 |
Operators are the essential pieces that allow complex asynchronous code to be easily composed in a declarative manner.
操作符是声明式处理复杂异步的重要基石。
📌 RxJS ships with operators. —— Ben Lesh
RxJS中操作符都是纯函数。官方提供的操作符基本上够日常开发使用,如果有特殊需求可以自定义操作符。
接收源Observable作为参数,返回新Observable。纯操作,即不改变源Observable。
管道操作符类别参考👉。
类别 | 说明📖 | 举例🌰 |
---|---|---|
组合 | 连接来自多个流的信息。基于值的顺序、时间和结构选择相应操作符。 | merge 合并流 concat 连接流 |
转换 | 基于源Observable的值转换为新Observable。 | map 转换流的值 switchMap 切换流 |
过滤 | 过滤源Observable的值。 | filter 过滤流的值 debounce 防抖 |
多播 | 单值流转换为多播流。 | multicast 使用Subject多播流 |
错误处理 | 处理流中的错误。 | retry 错误重试 catchError 捕获错误 |
工具 | 工具函数操作。 | tap 副作用(调试用) timestamp 打印流的值和时间戳 |
条件 | 对源Observable的条件处理。 | every 流每个值满足条件时返回true isEmpty 流是否为空 |
聚集 | 汇聚流的值得到新流。 | count 计数 reduce 类似数组reduce |
用来创建Observable的函数,如
of
:按顺序发出任意数量的值interval
:基于给定时间间隔发出数字序列fromEvent
:将DOM事件转化为ObservablefromPromise
:将Promise转化为ObservableScheduler controls when a subscription starts and when notifications are delivered.
调度器控制订阅开始时机、流的数据传播时机。
💡在大部分场景下,使用默认调度器即可。
类别 | 说明📖 | 举例🌰 |
---|---|---|
null | 默认调度器 | 同步、递归(常用) |
queueScheduler | 队列调度器 | 同步、队列 |
asapScheduler | 微任务调度器 | 异步、微任务 |
asyncScheduler | 异步调度器 | 异步、宏任务 |
animationFrameScheduler | 动画调度器 | window.requestAnimationFrame |
StackBlitz项目rxjs-drag
小球一次拖拽过程可描述为:
拖拽过程中触发的鼠标事件对应三个流:鼠标按下mousedown$
、鼠标移动mousemove$
、鼠标弹起mouseup$
。
拖拽流drag$
可描述如下:
mousedown$
触发拖拽,记录鼠标按下位置mousemove$
,记录小球初始位置,并在移动过程中计算小球移动位置mouseup$
触发时,终止(takeUntil)mousemove$
const mousedown$ = fromEvent<MouseEvent>(ball, 'mousedown').pipe(
map(getMouseEventPos)
);
const mousemove$ = fromEvent<MouseEvent>(document, 'mousemove').pipe(
map(getMouseEventPos)
);
const mouseup$ = fromEvent<MouseEvent>(document, 'mouseup');
const drag$ = mousedown$.pipe(
switchMap(initialPos => {
const { top, left } = ball.getBoundingClientRect();
return mousemove$.pipe(
map(({ x, y }) => ({
top: y - initialPos.y + top,
left: x - initialPos.x + left
})),
takeUntil(mouseup$)
);
})
);
小球订阅drag$
,更新自身位置。
drag$.subscribe(({ top, left }) => {
ball.style.top = `${top}px`;
ball.style.left = `${left}px`;
ball.style.bottom = '';
ball.style.right = '';
});
StackBlitz项目rxjs-toast
一次toast可描述如下:(约定toast停留时长为3s,且不支持手动隐藏)
将toast管理拆分为:
show$
:触发展示hide$
:到达停留时长,或新toast覆盖当前const click$ = fromEvent(document.getElementById('btn'), 'click');
const toast$ = click$.pipe(
switchMap(() => {
let hideByDuration = false;
const duration$ = timer(2000).pipe(
mapTo('hide by duration'),
tap(() => (hideByDuration = true))
);
return concat(of('show'), duration$).pipe(
finalize(() => {
if (!hideByDuration) {
console.log('hide by next');
}
})
);
})
);
toast$.subscribe(console.info);
RxJS在流程控制、多个异步协作等场景中均表现良好,使用RxJS写出的代码简洁高效。希望大家在技术选型时可多多考虑RxJS。
点击阅读原文,了解更多技术干货~