RxJS由浅入深——理解Observable与Subject

61 阅读6分钟

前言

上文中讲了SubscriptionSubscriber,而在整个Rxjs的体系中observer为观察者,Subscription充当清理管理器,Subscriber是清理管理器也是观察者,而Observable是扮演一个什么角色呢?你会发现这些都缺乏一个源头,观察谁?而Observable充当的就是可观察对象(数据生产者)。

Observable

如何创建Observable

1. 基础操作符

  1. of:将参数转换为 Observable 序列
const observable1 = of(1, 2, 3);
  1. from: 将数组、Promise 等转换为 Observable
const observable2 = from([1, 2, 3]);
  1. fromEvent: 将事件转换为 Observable
const clickObservable = fromEvent(document, 'click');

2. 自定义的方式

// 创建自定义的 Observable
const customObservable = new Observable<number>(subscriber => {
    // 设置定时器
    const intervalId = setInterval(() => {
        subscriber.next(Math.random());
    }, 1000);

    // 清理函数
    return () => {
        clearInterval(intervalId);
    };
});

关于Observable

既然已经会创建Observable了,那么我们在详细的聊一聊Observable 与前面几者的关系。 举个例子

Observable 就是主播(数据生产者)

Observer 就是观众(数据消费者)

Subscriber 是带有管理权限的观众(可以接收内容,也可以管理资源)

Subscription 是退出机制的管理(管理清理工作)

那整体的代码可能就如下

// 创建一个直播间(Observable)
const liveStream = new Observable<string>(subscriber => {
    // 主播开始产生内容
    subscriber.next('欢迎来到直播间!');
    
    const timer = setInterval(() => {
        subscriber.next('直播内容...');
    }, 1000);
    
    // 提供清理机制(关闭直播)
    return () => {
        clearInterval(timer);
        console.log('直播结束,清理资源');
    };
});

// 观众(Observer)进入直播间
liveStream.subscribe({
    next: content => console.log('观众收到:', content),
    error: err => console.log('直播出错:', err),
    complete: () => console.log('直播结束')
});

那么从这段代码中可以知道Observable的职责

  1. 产生数据
  2. 决定何时发送数据
  3. 决定何时结束
  4. 提供清理机制

本质

  1. 定义数据流
// Observable 本身只是定义了数据流的蓝图
const observable = new Observable<number>(subscribe => {
    // 这里的代码并不会立即执行
    // 只是定义了当有订阅发生时要做什么
});
  1. 提供订阅机制
class Observable<T> {
    // 核心是提供 subscribe 方法
    subscribe(observer?: Partial<Observer<T>>): Subscription {
        const subscriber = new Subscriber(observer);
        subscriber.add(this._subscribe(subscriber));
        return subscriber;
    }
}

这边其实也发现了subscribe的本质是充当了一个桥梁,它连接了ObservableSubscriber

  1. 支持清理逻辑的定义
const observable = new Observable(() => {
    // 返回清理逻辑
    return () => {
        console.log('清理');
    };
});

Observable和Subscriber相结合诞生的特点

Observable承担了数据生产者的角色,而Subscriber通过subscribe作为订阅者,接收数据、处理错误或完成通知。两者结合因此诞生了以下的特点

懒执行

const observable = new Observable(subscriber => {
    console.log('Observable 执行了');
    subscriber.next(1);
});
// 这时候不会打印任何东西

observable.subscribe(value => console.log(value));
// 这时才会执行并打印: 'Observable 执行了' 和 1

可取消

const subscription = observable.subscribe(value => console.log(value));
// 稍后可以取消订阅
subscription.unsubscribe();

单播

const observable = new Observable(subscriber => {
    const random = Math.random();
    subscriber.next(random);
});

// 每个订阅者获得不同的值
observable.subscribe(value => console.log('订阅者1:', value));
observable.subscribe(value => console.log('订阅者2:', value));

小结

最初一切从Subscription(清理管理器)开始,而Subscriber继承于Subscription并且实现了Observer,所以它具有清理管理器+数据处理的功能。

而数据的生产者Observable内部提供了一个桥梁subscribe来连接ObservableSubscriber的关系.

Subject

最后我们聊一下Subject,这个来说常见很多。先观察一下他的内部结构

class Subject<T> extends Observable<T> implements SubscriptionLike {
    // 属性
    observers: Observer<T>[];  // 存储观察者数组
    closed: boolean;          // 是否关闭
    isStopped: boolean;       // 是否停止
    hasError: boolean;        // 是否有错误
    thrownError: any;         // 错误信息

    // 方法
    next(value?: T): void;    // 发送值
    error(err: any): void;    // 发送错误
    complete(): void;         // 发送完成
    unsubscribe(): void;      // 取消订阅
}

可以发现他是继承于Observable具有发送数据的功能, 并且具有observers属性(数组),这就是意味着它具有向多个Observable发送数据的功能,也就是多播

多播

当多个观察者订阅了Subject时,在Subject发送数据后,观察者就能收到数据,例如以下代码

const subject = new Subject<number>();

// 多个观察者会收到相同的值
subject.subscribe(value => console.log('观察者1:', value));
subject.subscribe(value => console.log('观察者2:', value));

subject.next(1);
// 输出:
// 观察者1: 1
// 观察者2: 1

结合多播的特点往往会拥有以下的作用

事件总线(Event bus)

class EventBus {
    private subject = new Subject<any>();

    emit(event: any) {
        this.subject.next(event);
    }

    on(callback: (event: any) => void) {
        return this.subject.subscribe(callback);
    }
}

const eventBus = new EventBus();
eventBus.on(event => console.log('收到事件:', event));
eventBus.emit('hello');

例如:登录场景下,成功登录后需要将相应信息告诉别的画面比如用户信息,那么在登录成功后触发emit函数,在用户信息画面通过on来接收。

状态管理

import { BehaviorSubject, Observable } from 'rxjs';

class AuthStore {
    // 私有化Subject防止外部直接操作
    private _userState = new BehaviorSubject<{ isLoggedIn: boolean }>({ isLoggedIn: false });

    // 对外暴露Observable接口
    public get userState$(): Observable<{ isLoggedIn: boolean }> {
        return this._userState.asObservable();
    }

    // 受控的状态修改入口
    updateLoginStatus(isLoggedIn: boolean): void {
        this._userState.next({ isLoggedIn });
    }
}

// 使用示例
const authStore = new AuthStore();

// 订阅状态变化(组件层)
authStore.userState$.subscribe(state => {
    console.log('登录状态变更:', state.isLoggedIn);
});

// 修改状态(服务层)
authStore.updateLoginStatus(true);

其实状态管理与事件总线的实现方式很像,但这里说的是另一个小知识点,当使用Subject/BehaviorSubject作为状态管理时,通常会遇到使用SubjectasObservable方法将Subject转为Observable,这样的目的是保证数据源的访问权限,防止外部直接调用next()方法修改状态,确保状态变更的可控性

Subject的兄弟

谈到Subject必然少不了它的几个兄弟,接下来一一说明。

BehaviorSubject:有初始值,保存最新值

BehaviorSubject相较于Subject来说,在实际的开发过程中可能更常使用。因为在定义状态管理器时往往是需要一个默认值的,但Subject不具有这样的特点。
因此总结BehaviorSubject特点:

  1. 必须提供初始值,确保订阅者总能获得一个值,而使用Subject无法获取当前值
  2. 新订阅者可以立即获得最新值,不用等待下一次更新,而使用Subject新订阅者需要等待下一次数据更新才能显示内容
    例如
import { Subject, BehaviorSubject } from 'rxjs';

// 普通 Subject 示例
const subject = new Subject<number>();

// 订阅者只能获得订阅之后发出的值
subject.subscribe(value => {
  console.log('Subject subscriber 1:', value);
});

subject.next(1); // 输出: Subject subscriber 1: 1

// 后面的订阅者看不到之前的值
subject.subscribe(value => {
  console.log('Subject subscriber 2:', value);
});

subject.next(2);
// 输出: 
// Subject subscriber 1: 2
// Subject subscriber 2: 2

// BehaviorSubject 示例
const behaviorSubject = new BehaviorSubject<number>(0); // 需要提供初始值

// 第一个订阅者会立即获得当前值(0)
behaviorSubject.subscribe(value => {
  console.log('BehaviorSubject subscriber 1:', value);
}); // 输出: BehaviorSubject subscriber 1: 0

behaviorSubject.next(1); // 输出: BehaviorSubject subscriber 1: 1

// 新订阅者会立即获得最后发出的值(1)
behaviorSubject.subscribe(value => {
  console.log('BehaviorSubject subscriber 2:', value);
}); // 输出: BehaviorSubject subscriber 2: 1

behaviorSubject.next(2);
// 输出:
// BehaviorSubject subscriber 1: 2
// BehaviorSubject subscriber 2: 2

AsyncSubject:只发送最后一个值

const async = new AsyncSubject();
async.subscribe(x => console.log(x));
async.next(1);
async.next(2);
async.next(3);
async.complete();  // 只有在完成时才输出最后一个值: 3

ReplaySubject:可以回放指定数量的值

const replay = new ReplaySubject(2);  // 保存最近2个值
replay.next(1);
replay.next(2);
replay.next(3);
replay.subscribe(x => console.log(x));  // 输出: 2, 3

总结

到这里关于RxJS告一段落,整理了关于SubscriptionSubscriberSubscribeObserverObservable以及Subject这些常见的概念。主要是将这些知识点关联起来形成自己的图谱加深印象,并且在实际工作中使用以此加强理解。