RxJS 基础核心概念及操作符详解
1. RxJS 操作符基础
1.1 操作符组合
操作符是可组合的,能够将它们链接起来,使得可观察对象发出的项在到达观察者之前可以由一系列操作符进行处理。例如:
const beers = [
{name: "Stella", country: "Belgium", price: 9.50},
{name: "Sam Adams", country: "USA", price: 8.50},
{name: "Bud Light", country: "USA", price: 6.50}
];
from(beers)
.filter(beer => beer.price < 8)
.map(beer => `${beer.name}: $${beer.price}`)
.subscribe(
beer => console.log(beer),
err => console.error(err)
);
console.log("This is the last line of the script");
从 RxJS 6 开始,链接操作符的唯一方法是使用
pipe()
方法,将用逗号分隔的操作符作为参数传递给它。
1.2 可管道操作符
1.2.1 点链操作符的问题
在 RxJS 6 之前,可以使用点来链接操作符。但这种方式存在问题,例如导入点链操作符时:
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/filter';
这些操作符会修补
Observable.prototype
的代码,如果后续移除某个操作符但忘记移除对应的导入语句,打包工具在进行树摇时可能会保留未使用的代码。
1.2.2 可管道操作符的优势
RxJS 5.5 引入了可管道操作符,它们是纯函数,不会修补
Observable
。可以使用 ES6 导入语法导入操作符,然后将它们包装在
pipe()
函数中:
import {map, filter} from 'rxjs/operators';
import {from} from 'rxjs';
from(beers)
.pipe(
filter(beer => beer.price < 8),
map(beer => `${beer.name}: $${beer.price}`)
)
.subscribe(
beer => console.log(beer),
err => console.error(err)
);
这样,如果移除某个操作符,打包工具的树摇模块可以识别出未使用的导入函数,相应操作符的代码将不会包含在打包文件中。
1.3 异步可观察对象
默认情况下,
from()
函数返回同步可观察对象。如果需要异步可观察对象,可以使用第二个参数指定异步调度器:
from(beers, Scheduler.async)
这样修改后,会先打印 “This is the last line of the script”,然后再发出啤酒信息。
1.4 reduce 操作符
reduce
操作符允许聚合可观察对象发出的值。它有两个参数:一个累加器函数和一个初始值。例如:
const beers = [
{name: "Stella", country: "Belgium", price: 9.50},
{name: "Sam Adams", country: "USA", price: 8.50},
{name: "Bud Light", country: "USA", price: 6.50},
{name: "Brooklyn Lager", country: "USA", price: 8.00},
{name: "Sapporo", country: "Japan", price: 7.50}
];
from(beers)
.pipe(
map(beer => beer.price),
reduce( (total, price) => total + price, 0)
)
.subscribe(
totalPrice => console.log(`Total price: ${totalPrice}`)
);
运行此脚本将输出所有啤酒的总价。
reduce
操作符在可观察对象完成时发出聚合结果。
2. 使用观察者 API
2.1 观察者的定义
观察者是实现了
next()
、
error()
和
complete()
函数中的一个或多个的对象。例如:
const beerObserver = {
next: function(beer) { console.log(`Subscriber got ${beer.name}`)},
error: function(err) { console.err(error)},
complete: function() {console.log("The stream is over")}
}
2.2 可观察对象的创建
可以使用
create
方法创建可观察对象,传递一个代表观察者的参数:
const beerObservable$ = Observable.create( observer => observer.next(beer));
在订阅时,可观察对象才会知道具体提供的是哪个观察者。
2.3 完整示例
function getObservableBeer(){
return Observable.create( observer => {
const beers = [
{name: "Stella", country: "Belgium", price: 9.50},
{name: "Sam Adams", country: "USA", price: 8.50},
{name: "Bud Light", country: "USA", price: 6.50},
{name: "Brooklyn Lager", country: "USA", price: 8.00},
{name: "Sapporo", country: "Japan", price: 7.50}
];
beers.forEach( beer => observer.next(beer));
observer.complete();
});
}
getObservableBeer()
.subscribe(
beer => console.log(`Subscriber got ${beer.name}`),
error => console.err(error),
() => console.log("The stream is over")
);
在这个示例中,我们创建了一个冷可观察对象,因为数据生产者(啤酒数组)是在
getObservableBeer()
可观察对象内部创建的。每个订阅者在查询条件相同时,无论订阅时间如何,都会得到相同的啤酒数据。
2.4 调试可观察对象
tap
操作符可以对源可观察对象发出的每个值执行副作用(例如记录一些数据),但返回的可观察对象与源对象相同,可用于调试:
import { map, tap } from 'rxjs/operators';
myObservable$
.pipe(
tap(beer => console.log(`Before: ${beer}`)),
map(beer => `${beer.name}, ${beer.country}`),
tap(beer => console.log(`After: ${beer}`))
)
.subscribe(...);
3. 使用 RxJS Subject
3.1 Subject 的概念
RxJS Subject 是一个包含可观察对象和观察者的对象。可以使用
next()
方法将数据推送给其观察者,也可以订阅它。Subject 可以有多个观察者,适用于多播场景,即向多个订阅者发出一个值。
3.2 简单示例
const mySubject$ = new Subject();
const subscription1 = mySubject$.subscribe(...);
const subscription2 = mySubject$.subscribe(...);
mySubject$.next(123); // each subscriber gets 123
3.3 实际应用示例
考虑一个金融公司的场景,交易员可以下达股票买卖订单,订单需要同时发送给两个脚本:一个用于向证券交易所下单,另一个用于向交易委员会报告订单。
enum Action{
Buy = 'BUY',
Sell = 'SELL'
}
class Order{
constructor(public orderId: number, public traderId: number,
public stock: string, public shares: number, public action:Action){}
}
const orders$ = new Subject<Order>();
class Trader {
constructor(private traderId:number, private traderName:string){}
placeOrder(order: Order){
orders$.next(order);
}
}
const stockExchange = orders$.subscribe(
ord => console.log(`Sending to stock exchange the order to ${ord.action} ${ord.shares} shares of ${ord.stock}`)
);
const tradeCommission = orders$.subscribe(
ord => console.log(`Reporting to trade commission the order to ${ord.action} ${ord.shares} shares of ${ord.stock}`)
);
const trader = new Trader(1, 'Joe');
const order1 = new Order(1, 1,'IBM',100,Action.Buy);
const order2 = new Order(2, 1,'AAPL',100,Action.Sell);
trader.placeOrder( order1);
trader.placeOrder( order2);
运行上述代码,两个订阅者都会收到交易员下达的订单信息。
3.4 总结
通过使用 RxJS Subject,可以方便地实现多播功能,将一个值同时发送给多个订阅者。在实际应用中,它可以用于处理需要向多个地方发送数据的场景,如金融交易中的订单处理。
3.5 流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(Trader):::process -->|placeOrder| B(Subject - orders$):::process
B -->|next| C(Stock Exchange Subscriber):::process
B -->|next| D(Trade Commission Subscriber):::process
3.6 表格
| 元素 | 描述 |
|---|---|
Action
枚举
|
定义订单的操作类型,包括
Buy
和
Sell
|
Order
类
| 表示订单,包含订单 ID、交易员 ID、股票名称、股数和操作类型 |
orders$
主题
| 用于处理订单的主题,交易员下达的订单会通过它发送给订阅者 |
Trader
类
|
表示交易员,有
placeOrder
方法用于下达订单
|
stockExchange
订阅者
| 接收订单并向证券交易所发送订单信息 |
tradeCommission
订阅者
| 接收订单并向交易委员会报告订单信息 |
4. flatMap 操作符
4.1 flatMap 操作符的作用
在某些情况下,需要将可观察对象发出的每个项视为另一个可观察对象。
flatMap
操作符可以自动订阅外部可观察对象发出的每个项。它用于“将可观察对象发出的项转换为可观察对象,然后将这些可观察对象的发射结果展平为单个可观察对象”。
4.2 示例:处理饮料托盘
假设有一个可观察对象发出饮料托盘,每个托盘是一个可观察对象,我们要将这些托盘转换为包含单个饮料的输出流。
function getDrinks() {
const beers$ = from([
{name: "Stella", country: "Belgium", price: 9.50},
{name: "Sam Adams", country: "USA", price: 8.50},
{name: "Bud Light", country: "USA", price: 6.50}
], Scheduler.async);
const softDrinks$ = from([
{name: "Coca Cola", country: "USA", price: 1.50},
{name: "Fanta", country: "USA", price: 1.50},
{name: "Lemonade", country: "France", price: 2.50}
], Scheduler.async);
return Observable.create( observer => {
observer.next(beers$);
observer.next(softDrinks$);
observer.complete();
});
}
// We want to "unload" each palette and print each drink info
getDrinks()
.pipe(flatMap(drinks => drinks))
.subscribe(
drink => console.log(`Subscriber got ${drink.name}: ${drink.price}`),
error => console.err(error),
() => console.log("The stream of drinks is over")
);
运行此脚本将输出每个饮料的信息。
4.3 示例:处理 HTTP 请求
在 Angular 中,HTTP 请求返回可观察对象。当需要执行多个 HTTP 请求,且第一个请求的结果要用于第二个请求时,可以使用
flatMap
操作符。
import {flatMap} from 'rxjs/operators';
httpClient.get('/customers/123')
.pipe(
flatMap(customer => this.httpClient.get(customer.orderUrl))
)
.subscribe(response => this.order = response);
这样可以避免嵌套的
subscribe()
调用。
4.4 示例:监控交易员订单
考虑一个场景,有两个
Subject
实例:
traders$
用于跟踪交易员,
orders$
用于跟踪每个交易员下达的订单。作为经理,想要监控所有交易员下达的订单。
enum Action{
Buy = 'BUY',
Sell = 'SELL'
}
class Order{
constructor(public orderId: number, public traderId: number,
public stock: string, public shares: number, public action: Action)
{}
}
let traders$ = new Subject<Trader>();
class Trader {
orders$ = new Subject<Order>();
constructor(private traderId: number,
public traderName: string) {}
}
let tradersSubscriber = traders$.subscribe
(trader => console.log(`Trader ${trader.traderName} arrived`));
let ordersSubscriber = traders$
.pipe(flatMap(trader => trader.orders$))
.subscribe(ord =>
console.log(`Got order from trader ${ord.traderId} to ${ord.action} ${ord.shares} shares of ${ord.stock}`)
);
let firstTrader = new Trader(1, 'Joe');
let secondTrader = new Trader(2, 'Mary');
traders$.next(firstTrader);
traders$.next(secondTrader);
let order1 = new Order(1,1,'IBM',100,Action.Buy);
let order2 = new Order(2,1,'AAPL',200,Action.Sell);
let order3 = new Order(3,2,'MSFT',500,Action.Buy);
// Traders place orders
firstTrader.orders$.next(order1);
firstTrader.orders$.next(order2);
secondTrader.orders$.next(order3);
运行此代码,订阅者将收到所有交易员下达的订单信息。
4.5 流程图
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(traders$ 主题):::process -->|next| B(Trader 实例):::process
B -->|orders$ 主题| C(flatMap 操作符):::process
C -->|展平结果| D(ordersSubscriber 订阅者):::process
4.6 表格
| 元素 | 描述 |
|---|---|
Action
枚举
|
定义订单的操作类型,包括
Buy
和
Sell
|
Order
类
| 表示订单,包含订单 ID、交易员 ID、股票名称、股数和操作类型 |
traders$
主题
| 用于跟踪交易员的主题 |
Trader
类
|
表示交易员,每个交易员有自己的
orders$
主题用于跟踪订单
|
orders$
主题
| 每个交易员的主题,用于处理该交易员下达的订单 |
tradersSubscriber
订阅者
| 接收交易员到达的信息 |
ordersSubscriber
订阅者
| 接收所有交易员下达的订单信息 |
总结
本文详细介绍了 RxJS 的一些核心概念和操作符,包括操作符的组合、可管道操作符、
reduce
操作符、观察者 API、RxJS Subject 和
flatMap
操作符。通过具体的代码示例和流程图、表格,展示了这些概念和操作符的使用方法和应用场景。掌握这些知识可以帮助开发者更好地处理异步数据流,实现复杂的响应式编程。
操作步骤总结
-
操作符组合
:使用
pipe()方法将多个操作符组合起来处理可观察对象发出的项。 -
可管道操作符
:使用 ES6 导入语法导入操作符,然后将它们包装在
pipe()函数中,便于打包工具进行树摇优化。 - reduce 操作符 :通过指定累加器函数和初始值,聚合可观察对象发出的值。
-
观察者 API
:创建观察者对象,实现
next()、error()和complete()函数,使用create方法创建可观察对象并订阅。 -
RxJS Subject
:创建
Subject实例,使用next()方法推送数据,多个订阅者可以接收相同的值。 - flatMap 操作符 :自动订阅外部可观察对象发出的每个项,将内部可观察对象的发射结果展平为单个可观察对象。
通过实际代码示例和操作步骤的总结,希望读者能够更好地理解和应用 RxJS 的相关知识。
超级会员免费看
18

被折叠的 条评论
为什么被折叠?



