2.10 Observables 与 RxJS RxJS 是基于可观察序列的异步编程库,Angular 的 HTTP、表单状态流、路由参数流全部构建其上。本节是第 2 章的收口:把前几节反复出现的"流"讲透——冷与热、常用操作符、订阅生命周期管理。变更检测视角下,RxJS 是控制"变更发生频率与合并方式"的调度台:防抖、节流、切换、合并,全在这里完成。 前两节的状态仓库用到了 BehaviorSubject 与操作符,本节补齐底层语言,第 2 章至此十节闭环。 带着一个问题进入 带着一个问题进入:2.8 与 2.9 里反复出现的"订阅之后记得清理"到底在防什么?答案藏在流的机制里: 解释冷热可观察的区别,说明 HTTP 与 Subject 各属哪类。
RxJS 是基于可观察序列的异步编程库,Angular 的 HTTP、表单状态流、路由参数流全部构建其上。本节是第 2 章的收口:把前几节反复出现的"流"讲透——冷与热、常用操作符、订阅生命周期管理。变更检测视角下,RxJS 是控制"变更发生频率与合并方式"的调度台:防抖、节流、切换、合并,全在这里完成。
前两节的状态仓库用到了 BehaviorSubject 与操作符,本节补齐底层语言,第 2 章至此十节闭环。
带着一个问题进入:2.8 与 2.9 里反复出现的"订阅之后记得清理"到底在防什么?答案藏在流的机制里:
import { of, interval, Subject } from 'rxjs'; // 冷可观察:每个订阅者触发独立执行(HttpClient 请求即此类) const cold$ = interval(1000); // 两个订阅者各自从 0 数起,互不相干 // 热可观察:无论几个订阅者,执行只有一份,后进者错过已发射的值 const hot$ = new Subject<number>(); hot$.next(1); // 此刻无人订阅,值永久丢失 hot$.subscribe(v => console.log('A', v)); hot$.next(2); // 只有 A 收到 hot$.subscribe(v => console.log('B', v)); hot$.next(3); // A、B 都收到 // BehaviorSubject 是 Subject 的变体:新订阅者立即收到最近一个值
冷热之别解释了很多"灵异现象":同一个 HTTP 流订阅两次发出两次请求;晚订阅的组件拿不到早发射的值。2.9 节的 BehaviorSubject 仓库正是为了给晚进页面的组件补发当前值。
import { Subject, fromEvent } from 'rxjs'; import { map, filter, switchMap, debounceTime, takeUntil } from 'rxjs/operators'; export class SearchPanel { private api = inject(OrderApi); private destroy$ = new Subject<void>(); // 销毁信号源 constructor() { fromEvent<InputEvent>(this.inputEl(), 'input').pipe( map(e => (e.target as HTMLInputElement).value.trim()), filter(v => v.length >= 2), // 过滤:不合格的值到此为止 debounceTime(300), // 防抖:停顿 300ms 才放行 switchMap(v => this.api.list(v, 1)), // 切换:新值取消旧请求 takeUntil(this.destroy$) // 销毁信号一到,整条链终止 ).subscribe(page => { this.result = page.items; // 订阅回调属于异步事件,检查随后到来(第 2.8 节结论复用) }); } ngOnDestroy() { this.destroy$.next(); // 通知所有 takeUntil 停止 this.destroy$.complete(); } inputEl(): HTMLInputElement { return document.querySelector('#kw') as HTMLInputElement; } result: unknown[] = []; }
五个操作符一句话记忆:map 换值、filter 挡值、debounceTime 并发 Value合并成"最后一次"、switchMap 新旧二选一、takeUntil 统一断电。前四个在 2.4/2.7/2.8 节都以单点形式出现过,本节把它们串成一条链——真实项目的搜索框就是这条链。
每个 subscribe 返回订阅对象,未取消的订阅在组件销毁后仍持有回调与组件引用。三类高危源:HTTP 流(未完成的会话流尤其)、fromEvent 绑定、Interval 类无限流。管理策略按代价排序:
💡 判断泄漏的土办法:组件销毁钩子里打日志,操作触发源(比如再点一次按钮)看控制台是否还有旧组件的输出——有就是泄漏。

背景:运维看板页有六个指标卡,每个卡各自轮询一个接口,间隔 5 秒。上线后发现 CPU 占用异常:六条轮询流各自触发检查,页面几乎每秒都在检查。
操作:合并轮询为单一节拍,一次拉取全部指标:
import { timer, forkJoin } from 'rxjs'; import { switchMap } from 'rxjs/operators'; metrics$ = timer(0, 5000).pipe( // 单节拍:立即执行一次,之后每 5 秒 switchMap(() => forkJoin({ // 汇聚六个请求为一个对象 cpu: this.api.cpu(), mem: this.api.memory(), disk: this.api.disk(), net: this.api.network(), qps: this.api.qps(), err: this.api.errors() })), takeUntil(this.destroy$) ); // 模板:六个卡片全部绑定 metrics$ | async 的字段
结果:检查频率从"每秒可能多次"降到"每 5 秒一次",且六卡数据同帧更新,消除了轮流闪跳。
解读:这是"调度台"论点的实证——检查次数不由数据量决定,而由变更提交的次数决定;把多次提交合并成一次,是继"降低每轮检查成本"(纯管道、trackBy)之外的第二条优化路线。两条路线将在第 3 章合流。
变式:指标重要性不同时用两条不同周期的节拍流(快线 5 秒、慢线 30 秒),仍然远好于六条独立流。
第 2 章完。下一章拆开发动机:变更检测机制深度剖析。