RxJS 5.5 在上週已經發布了 beta.7 版本,在 5.5 中 RxJS 引入了 lettable operator
這一個新特性。依靠這個特性,RxJS 中的流與操作符可以用更加 FP 的風格組合起來。
lettable operator 試圖解決的問題
在之前版本的 RxJS 5 中,操作符通過 dot chaining 的方式進行組合。在考慮 bundle size 的場合,RxJS 的操作符往往以
import "rxjs/add/operator/SOME_OPERATOR"
(instace operator)import "rxjs/add/observable/SOME_OPERATOR"
(static operator)
的方式加入應用程式中。通過這種方法,避免了把完整的 RxJS 全部打包。
這種 rxjs/add
風格的引入,相當於在 RxJS 庫外動態地將操作符加入 Observable
和 Observable.prototype
當中。這也就使得 RxJS 幾乎不能受益於 webpack 2 或者 rollup 的 tree-shaking。
此外,如果沒有使用引入的操作符,TypeScript、Flow 或者各種 linter 是不會因此報錯的。考慮到 RxJS 中琳琅滿目的操作符,很多時候會在編碼過程中不斷更換操作符,有時候是會忘記刪除引入的操作符,導致 bundle size 的增加。
lettable operator 的使用方法
lettable operator
這個名字是 RxJS 社群中的黑話,RxJS 社群中有 issue 在討論是不是要改一個對初學者更友好的名字。
lettable operator
需要配合 Observable.prototype.pipe
一起使用,下面來看一個使用 lettable operator
的例子:
import { range } from `rxjs/observable/range`;
import { map, filter, scan } from `rxjs/operators`;
const source$ = range(0, 10);
source$.pipe(
filter(x => x % 2 === 0),
map(x => x + x),
scan((acc, x) => acc + x, 0)
)
.subscribe(x => console.log(x))複製程式碼
相對的,dot chaining 風格的寫法如下:
import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/range";
import "rxjs/add/operator/map";
import "rxjs/add/operator/filter";
import "rxjs/add/operator/scan";
const source$ = Observable.range(0, 10);
source$
.filter(x => x % 2 === 0)
.map(x => x + x),
.scan((acc, x) => acc + x, 0)
.subscribe(x => console.log(x));複製程式碼
以 lettable operator
構成的 pipeline 要比 dot chaining 更貼近於現在流行的 FP 風格。更重要的是,這種方法在引入操作符時比 dot chaining 的 rxjs/add/*
風格方便實用得多。
因此,也有相關的 issue 在討論是否將 rxjs/add/*
風格的引入方式在 6.0 中釋出到另外的 NPM package 中。
lettable operator 的原理
Observable.prototype.pipe
的程式碼如下:
import { pipeFromArray } from `./util/pipe`;
class Observable<T> implements Subscribable<T> {
pipe<R>(...operations: OperatorFunction<T, R>[]): Observable<R> {
if (operations.length === 0) {
return this as any;
}
return pipeFromArray(operations)(this);
}
}複製程式碼
在此呼叫了 ./util/pipe
中由 pipeFromArray
這個高階函式所返回的函式:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (!fns) {
return noop as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input);
};
}複製程式碼
在這一過程中,piped
拿到了 Observable 的 this
,並以此作為之後 reduce 過程中的初始值。
在這個 reduce
方法結束後,就得到了需要的 Observable。