前言
最近在巩固自己的知识体系,看到Rxjs
的时候每次都很别扭,很容易忘记,接下来就详细的聊一下RxJS
响应式编程
谈RxJS
离不开响应式编程,那么如何理解响应式编程呢?
概念:
响应式编程是一种基于数据流的范式编程,它强调“数据随时间推移的变化”,并通过观察者模式和声明式管道(pipe) 处理异步事件。相比于传统的命令式编程,它避免了手动管理状态和回调地狱,通过RxJS使事件处理更加可组合和可维护。
例子
假设我们有一个按钮,点击后触发事件,并将按钮点击次数展示在页面上:
响应式编程
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';
const button = document.querySelector('button');
const clickStream = fromEvent(button, 'click');
clickStream.pipe(
map((event, index) => index + 1) // 计算点击次数
).subscribe(count => {
console.log(`按钮被点击了 ${count} 次`);
});
响应式编程的特点:
- 声明式:我们声明了数据流的行为,而不需要关心其具体的执行步骤。
- 组合性:通过管道(pipe)操作可以轻松地对事件流进行操作(例如:过滤、转换、合并等)。
命令式编程
let count = 0;
const button = document.querySelector('button');
button.addEventListener('click', function() {
count++;
console.log(`按钮被点击了 ${count} 次`);
});
- 逐步执行:需要显式地定义每个步骤(如何增加计数、如何展示等)。
- 回调函数:处理异步事件时,需要显式使用回调函数来管理每次事件的处理逻辑。
- 手动管理状态:状态变量(如 count)需要手动进行更新。
从类型文件中进行追溯
说回RxJS
,这是很常见使用RxJS
返回Observable
来处理http请求
// 定义接口
public testHttp(): Observable<any> {
const result = this.http.get('xxxxxxxxxxxxxxxxxxx', {});
return result;
}
// 在具体的画面中调用接口
this.http.testHttp().subscribe((res: any) => {
console.log('res',res);
});
这里涉及到两个概念Observable
与subscribe
接下去我们在ide上点选到Observable
的类型定义文件Observable.d.ts
可以看到Observable
的构造函数接受了subscribe
作为参数
而subscribe
为一个函数,提供了两个入参this和subscriber,而根据ts的语法this用于声明函数内部的 this 上下文类型,它不是实际的参数
,所以存在一个subscriber的参数。
Subscription
而如果你点击subscriber的类型中查看会发现,subscriber 继承于 Subscription
即使在点击Subscription
的定义中发现也没有在继承谁了,那么就从这开始说起
我们先简化一下Subscription
结构并以interface的方式展示
interface Subscription {
closed: boolean;
unsubscribe(): void;
add(teardown: TeardownLogic): void;
remove(teardown: TeardownLogic): void;
}
closed
:表示订阅是否已关闭unsubscribe
:表示取消订阅add
:表示添加清理逻辑remove
:表示移除清理逻辑
而Subscription
的本质是一个清除管理器。
如何理解“清理管理器”?
用一个例子来解释,想象 Subscription 是一个退房清单管理器
// 创建一个退房清单管理器
const moveOutChecklist = new Subscription();
// 添加需要处理的事项
moveOutChecklist.add(() => {
console.log('断水');
});
moveOutChecklist.add(() => {
console.log('断电');
});
moveOutChecklist.add(() => {
console.log('退还钥匙');
});
// 可以移除某个事项
const cleanRoom = () => console.log('打扫房间');
moveOutChecklist.add(cleanRoom);
moveOutChecklist.remove(cleanRoom); // 决定不打扫了
// 执行退房
moveOutChecklist.unsubscribe();
// 输出:
// 退还钥匙
// 断电
// 断水
这么看你会觉得,这更像是一个事务管理器,和清理(TearDown)
有什么关系?放眼于整个Rxjs当中,他主要是用来处理资源的释放和清理。比如:
// 例如,当我们开启了一些资源
const subscription = new Subscription();
// 开启了一个定时器
const timer = setInterval(() => {
console.log('tick');
}, 1000);
// 添加定时器的清理逻辑
subscription.add(() => {
clearInterval(timer); // 这就是清理操作
});
// 打开了一个文件
const file = openFile(); // 假设这是打开文件的操作
subscription.add(() => {
file.close(); // 这是清理操作
});
// 当我们不再需要这些资源时
subscription.unsubscribe(); // 执行所有清理操作
作用
因此总结 Subscription
的作用
- 管理清理函数的集合
const subscription = new Subscription();
// 添加清理函数
subscription.add(() => console.log('清理A'));
subscription.add(() => console.log('清理B'));
// 执行所有清理函数
subscription.unsubscribe();
// 输出:
// 清理B
// 清理A
- 提供统一的清理接口
class ResourceManager {
private subscription = new Subscription();
addCleanup(cleanup: () => void) {
// 统一添加清理逻辑
this.subscription.add(cleanup);
}
removeCleanup(cleanup: () => void) {
// 移除特定的清理逻辑
this.subscription.remove(cleanup);
}
cleanup() {
// 统一执行所有清理
this.subscription.unsubscribe();
}
}
- 跟踪清理状态
const subscription = new Subscription();
console.log(subscription.closed); // false
subscription.unsubscribe();
console.log(subscription.closed); // true
Subscriber
在实际应用中接触Subscription
可能少之又少,但是接触Subscriber
就相对常见了
因为上面也提到了 Subscriber
继承的 Subscription
,但它还实现了Observer
先说说Observer(观察者)
,Observer
的interface相对简单很多
export interface Observer<T> {
closed?: boolean;
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
然后我们把Observer
、Subscription
、Subscriber
三者的结构进行简化整理得到
// Observer 接口定义
interface Observer<T> {
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
// Subscription 的基本结构
interface Subscription {
closed: boolean;
unsubscribe(): void;
add(teardown: TeardownLogic): void;
remove(teardown: TeardownLogic): void;
}
// Subscriber 的基本结构(继承自 Subscription)
class Subscriber<T> extends Subscription implements Observer<T> {
protected destination: Observer<T>; // 新增:目标观察者
closed: boolean; // 继承
next(value: T): void; // 新增:处理数据
error(err: any): void; // 新增:处理错误
complete(): void; // 新增:处理完成
unsubscribe(): void; // 重写:除了清理还要处理状态
}
通过结构可以发现,Subscriber
还具有处理数据流的能力(next
、error
、complete
),而这一切归功于实现了Observer
。 那么也可以得到Subscriber
的本质:清理管理器+观察者。
这种设计使得 Subscriber 可以:
- 处理数据流:通过函数
next
、error
、complete
- 管理资源清理:通过函数
add
、remove
、unsubscribe
在举一个例子加深一下大家的理解
// 创建 Observable
const observable = new Observable<number>(subscriber => {
// subscriber 是 Subscriber 的实例
// 1. 可以发送值
subscriber.next(1);
// 2. 可以添加清理逻辑
const timer = setInterval(() => {
subscriber.next(Math.random());
}, 1000);
// 添加清理逻辑
subscriber.add(() => {
clearInterval(timer);
});
});
// 而普通的 Subscription 只能用于清理管理
const subscription = new Subscription();
subscription.add(() => console.log('清理'));
总结
文章主要介绍了响应式编程
Subscription
以及 Subscriber
三个概念,看完之后你可能觉得还是云里雾里的很难理解,这是很正常的,RxJS
响应式编程本身就是一个抽象的概念,不妨动手试试,只需要对着各个概念通过ide点进去查看相关的类型文件,再结合文章,回过头看看。