RxJS 管道运算符

2020-10-09 15:31 更新

从5.5版开始,我们已经发布了“可移植运算符”,可以在 进行访问 rxjs/operators(请注意复数的“运算符”)中 。 与 找到的“ patch”运算符相比,这些方法是仅引入所需运算符的更好方法 rxjs-compat打包中 。

注意 :使用 rxjsrxjs/operators不更改构建过程可能会导致捆绑包增大。 请参阅 已知问题” 下面的“ 部分。

重命名运营商

由于使运算符独立于 Observable 可用,因此运算符名称不能与 JavaScript 关键字限制冲突。 因此,某些运算符的可移植版本的名称已更改。 这些运算符是:

  1. do-> tap
  2. catch-> catchError
  3. switch-> switchAll
  4. finally-> finalize

let运算符现在是 一部分, Observableas 的 pipe无法导入。

source$.let(myOperator) -> source$.pipe(myOperator)

请参阅下面的“ 建立自己的运营商 ”。

toPromise()“操作员”已被删除 因为运算符返回 Observable, 不是一个 Promise。 现在有一个 Observable.toPromise()实例方法。

为什么?

修补的点链运算符的问题是:

  1. 导入修补程序运算符的任何库 增加 Observable.prototype都将为该库的所有使用者 ,从而造成盲目依赖。 如果库删除了它们的用法,它们将在不知不觉中破坏其他所有人。 使用 pipeable,必须将所需的运算符导入使用它们的每个文件中。

  1. 直接 到原型的操作员无法 “摇晃树”汇总 通过汇总或 Webpack 之类的工具 。 可管道运算符将像直接从模块中引入的函数一样。

应用程序中导入的未使用的运算符无法通过任何类型的构建工具或 lint 规则可靠地检测到。 这意味着您可以导入 scan,但停止使用它,并且仍将其添加到您的输出包中。 对于可管道运算符,如果您不使用它,则可以使用皮棉规则为您选择它。

功能组成很棒。 建立您自己的自定义运算符变得容易得多,现在它们可以正常工作,并且看起来就像 rxjs 中的所有其他运算符一样。 您不需要扩展 Observable 或覆盖它 lift

什么?

什么是管道运算符? 简而言之,可以与当前 一起使用的函数 let运算符 。 它曾经是名称(“ lettable”)的起源,但是这令人困惑,因此我们现在将它们称为“ pipeable”,因为它们打算与 一起使用 pipe实用程序 。 可管道运算符基本上是任何返回带有签名的函数的函数: <T, R>(source: Observable<T>) => Observable<R>

有一个 的 pipe内置 方法 Observable现在, Observable.prototype.pipe可以用来像与点链接所用的相似方式组成运算符(如下所示)。

pipe可以从导入实用程序功能 import { pipe } from 'rxjs';。 该 pipe功能可用于从其他管道运算符构建可重用的管道运算符。 例如:

import { pipe } from 'rxjs';
import { map } from 'rxjs/operators';


const mapTwice = <T,R>(fn: (value: T, index: number) => R) => pipe(map(fn), map(fn));

用法

您可以在 下从一个位置引入所需的任何运算符 'rxjs/operators'复数! ) 。 还建议直接使用所需的 Observable 创建方法,如下所示 range

import { range } from 'rxjs';
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))


// Logs:
// 0
// 4
// 12
// 24
// 40

轻松建立自己的运营商

实际上,您 始终 来 可以使用 做到这一点, let... 但是构建自己的运算符现在就像编写函数一样简单。 注意,您可以将自定义运算符与其他 rxjs 运算符无缝组合在一起。

import { Observable, interval } from 'rxjs';
import { filter, map, take, toArray } from 'rxjs/operators';


/**
 * an operator that takes every Nth value
 */
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(); }
    })
  });


/**
 * You can also use an existing operator like so
 */
const takeEveryNthSimple = (n: number) => <T>(source: Observable<T>) =>
  source.pipe(filter((value, index) => index % n === 0 ))


/**
 * And since pipeable operators return functions, you can further simplify like so
 */
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));
// Logs:
// [0, 2304, 9216]

已知的问题

打字稿<2.4

在 TypeScript 2.3及更低版本中,由于无法在 TypeScript 2.4之前推断类型,因此需要将类型添加到传递给运算符的函数中。 在 TypeScript 2.4中,类型将通过合成正确推断。

TS 2.3 and under

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,但是在不更改生成过程的情况下这样做通常会导致应用程序包更大。 这是因为默认情况下 rxjs/operators将解析为 rxjs 的 CommonJS 输出。

为了使用新的管道运算符而不增加捆绑包的大小,您将需要更改 Webpack 配置。 这仅适用于 Webpack 3+,因为它依赖于 的新 ModuleConcatenationPluginWebpack 3 中 功能。

路径映射

与 rxjs 5.5 一起发布的是带有 ES5 和 ES2015 语言级别的 ECMAScript 模块格式(导入和导出)的 rxjs 构建。 您可以在 找到这些分布 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: {
    // Use the "alias" key to resolve to an ESM distribution
    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;
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号