原文連接: github.com/ReactiveX/r…css
本文爲 RxJS 中文社區 翻譯文章,如需轉載,請註明出處,謝謝合做!html
若是你也想和咱們一塊兒,翻譯更多優質的 RxJS 文章以奉獻給你們,請點擊【這裏】node
寫在前面的話: (非正文) 去年9月,RxJS5.5beta版本發佈了 lettable 操做符這個新特性,而就在幾天前由於沒法忍受此名稱,又將 lettable 更爲爲 pipeable,詳細請見 PR#3224 。webpack
前段時間忙於翻譯 PWA 一書,所以 RxJS 的專欄擱置了一小段時間,週末抽了些時間出來將官方文檔 Pipeable Operators 翻譯出來同步至中文文檔。此特性仍是比較重要,能夠說是 RxJS 將來的走向,下面請看正文。git
從5.5版本開始咱們提供了 「pipeable 操做符」,它們能夠經過 rxjs/operators
來訪問 (注意 "operators" 是複數)。相比較於經過在 rxjs/add/operator/*
中以「打補丁」的方式來獲取須要用到的操做符,這是一種更好的方式。github
注意: 若是使用 rxjs/operators
而不修改構建過程的話會致使更大的包。詳見下面的已知問題一節。web
重命名的操做符數組
因爲操做符要從 Observable 中獨立出來,因此操做符的名稱不能和 JavaScript 的關鍵字衝突。所以一些操做符的 pipeable 版本的名稱作出了修改。這些操做符是:bash
do
-> tap
catch
-> catchError
switch
-> switchAll
finally
-> finalize
pipe
是 Observable
的一部分,不須要導入,而且它能夠替代現有的 let
操做符。app
source$.let(myOperator) -> source$.pipe(myOperator)
參見下面的「構建本身的操做符」。
以前的 toPromise()
「操做符」已經被移除了,由於一個操做符應該返回 Observable
,而不是 Promise
。如今使用 Observable.toPromise()
的實例方法來替代。
由於 throw
是關鍵字,你能夠在導入時使用 _throw
,就像這樣: import { _throw } from 'rxjs/observable/throw'
。
若是前綴_
使你困擾的話 (由於通常前綴_
表示「內部的 - 不要使用」) ,你也能夠這樣作:
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
...
const e = ErrorObservable.create(new Error('My bad'));
const e2 = new ErrorObservable(new Error('My bad too'));
複製代碼
打補丁的操做符主要是爲了鏈式調用,但它存在以下問題:
任何導入了補丁操做符的庫都會致使該庫的全部消費者的 Observable.prototype
增大,這會建立一種依賴上的盲區。若是此庫移除了某個操做符的導入,這會在無形之中破壞其餘全部人的使用。使用 pipeable 操做符的話,你必須在每一個用到它們的頁面中都導入你所須要用到的操做符。
經過打補丁的方式將操做符掛在原型上是沒法經過像 rollup 或 webpack 這樣的工具進行「搖樹優化」 ( tree-shakeable ) 。而 pipeable 操做符只是直接從模塊中提取的函數而已。
對於在應用中導入的未使用過的操做符,任何類型的構建工具或 lint 規則都沒法可靠地檢測出它們。例如,好比你導入了 scan
,但後來再也不使用了,但它仍會被添加到打包後的文件中。使用 pipeable 操做符的話,若是你再也不使用它的簡化,lint 規則能夠幫你檢測到。
函數組合 ( functional composition )很棒。建立自定義操做符也變得很是簡單,它們就像 rxjs 中的其餘全部操做符同樣。你再也不須要擴展 Observable 或重寫 lift
。
簡而言之,就是能夠與當前的 let
操做符一塊兒使用的函數。不管名稱起的是否合適,這就是它的由來。基本上來講,pipeable 操做符能夠是任何函數,可是它須要返回簽名爲 <T, R>(source: Observable<T>) => Observable<R>
的函數。
如今 Observable
中有一個內置的 pipe
方法 (Observable.prototype.pipe
),它能夠用相似於以前的鏈式調用的方式來組合操做符 (以下所示)。
There is also a pipe
utility function at rxjs/util/pipe
that can be used to build reusable pipeable operators from other pipeable operators.
在 rxjs/util/pipe
中還有一個名爲 pipe
的工具函數,它可用於構建基於其餘 pipeable 操做符的可複用的 pipeable 操做符。
你只需在 'rxjs/operators'
(注意是複數!) 中便能提取出所須要的任何操做符。還推薦直接導入所需的 Observable 建立操做符,以下面的 range
所示:
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))
複製代碼
實際上,你能夠一直用 let
來完成...,可是如今建立自定義操做符就像寫個函數同樣簡單。注意,你能夠將你的自定義操做符和其餘的 rxjs 操做符無縫地組合起來。
import { interval } from 'rxjs/observable/interval';
import { filter, map, take, toArray } from 'rxjs/operators';
/** * 取每第N個值的操做符 */
const takeEveryNth = (n: number) => <T>(source: Observable<T>) =>
new Observable<T>(observer => {
let count = 0;
return source.subscribe({
next(x) {
if (count++ % n === 0) observer.next(x);
},
error(err) { observer.error(err); },
complete() { observer.complete(); }
})
});
/** * 還可使用現有的操做符 */
const takeEveryNthSimple = (n: number) => <T>(source: Observable<T>) =>
source.pipe(filter((value, index) => index % n === 0 ))
/** * 由於 pipeable 操做符返回的是函數,還能夠進一步簡化 */
const takeEveryNthSimplest = (n: number) => filter((value, index) => index % n === 0);
interval(1000).pipe(
takeEveryNth(2),
map(x => x + x),
takeEveryNthSimple(3),
map(x => x * x),
takeEveryNthSimplest(4),
take(3),
toArray()
)
.subscribe(x => console.log(x));
// [0, 12, 24]
複製代碼
在2.3及如下版本的 TypeScript 中,須要在傳遞給操做符的函數中添加類型,由於 TypeScript 2.4以前的版本沒法推斷類型。在TypeScript 2.4中,類型能夠經過組合來正確地推斷出來。
TS 2.3及如下版本
range(0, 10).pipe(
map((n: number) => n + '!'),
map((s: string) => 'Hello, ' + s),
).subscribe(x => console.log(x))
複製代碼
TS 2.4及以上版本
range(0, 10).pipe(
map(n => n + '!'),
map(s => 'Hello, ' + s),
).subscribe(x => console.log(x))
複製代碼
當從清單文件導入(或從新導出)時,應用的打包文件有時會增大。如今能夠從 rxjs/operators
導入 pipeable 操做符,但若是不更新構建過程的話,會常常致使應用的打包文件更大。這是由於默認狀況下 rxjs/operators
會解析成 rxjs 的 CommonJS 輸出。
爲了使用新的 pipeable 操做符而不增長打包尺寸,你須要更新 Webpack 配置。這隻適用於 Webpack 3+ ,由於須要依賴 Webpack 3中的新插件 ModuleConcatenationPlugin
。
路徑映射
伴隨 rxjs 5.5版本一同發佈的是使用ES5 和 ES2015 兩種語言級別的 ECMAScript 模塊格式 (導入和導出)。你能夠在 node_modules/rxjs/_esm5
和 node_modules/rxjs/_esm2015
下面分別找到這兩個分發版本 ("esm"表示 ECMAScript 模塊,數字"5"或"2015"表明 ES 語言級別)。在你的應用源碼中,你應該從 rxjs/operators
導入,但在 Webpack 配置文件中,你須要將導入從新映射爲 ESM5 (或 ESM2015) 版本。
若是 require('rxjs/_esm5/path-mapping')
,你將接收一個函數,該函數返回一個鍵值對的對象,該對象包含每一個輸入映射到磁盤上的文件位置。像下面這樣使用該映射:
webpack.config.js
簡單配置:
const rxPaths = require('rxjs/_esm5/path-mapping');
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: 'index.js',
output: 'bundle.js',
resolve: {
// 使用 "alias" 鍵來解析成 ESM 分發版
alias: rxPaths()
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};
複製代碼
更多完整配置 (接近真正場景):
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const DashboardPlugin = require('webpack-dashboard/plugin');
const nodeEnv = process.env.NODE_ENV || 'development';
const isProd = nodeEnv === 'production';
const rxPaths = require('rxjs/_esm5/path-mapping');
var config = {
devtool: isProd ? 'hidden-source-map' : 'cheap-eval-source-map',
context: path.resolve('./src'),
entry: {
app: './index.ts',
vendor: './vendor.ts'
},
output: {
path: path.resolve('./dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
devtoolModuleFilenameTemplate: function (info) {
return "file:///" + info.absoluteResourcePath;
}
},
module: {
rules: [
{ enforce: 'pre', test: /\.ts$|\.tsx$/, exclude: ["node_modules"], loader: 'ts-loader' },
{ test: /\.html$/, loader: "html" },
{ test: /\.css$/, loaders: ['style', 'css'] }
]
},
resolve: {
extensions: [".ts", ".js"],
modules: [path.resolve('./src'), 'node_modules'],
alias: rxPaths()
},
plugins: [
new webpack.DefinePlugin({
'process.env': { // eslint-disable-line quote-props
NODE_ENV: JSON.stringify(nodeEnv)
}
}),
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({
title: 'Typescript Webpack Starter',
template: '!!ejs-loader!src/index.html'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin({
mangle: false,
compress: { warnings: false, pure_getters: true, passes: 3, screw_ie8: true, sequences: false },
output: { comments: false, beautify: true },
sourceMap: false
}),
new DashboardPlugin(),
new webpack.LoaderOptionsPlugin({
options: {
tslint: {
emitErrors: true,
failOnHint: true
}
}
})
]
};
module.exports = config;
複製代碼
沒法控制構建過程
若是你沒法控制構建過程(或者沒法更新至 Webpack 3+)的話,上述解決方案將不適合你。因此,從 rxjs/operators
導入極可能讓應用的打包文件尺寸更大。但仍是有解決辦法的,你須要使用更深一層的導入,有點相似於5.5版本以前導入 pipeable 操做符的方式。
將:
import { map, filter, reduce } from 'rxjs/operators';
複製代碼
變成:
import { map } from 'rxjs/operators/map';
import { filter } from 'rxjs/operators/filter';
import { reduce } from 'rxjs/operators/reduce';
複製代碼