RxJS 5.x 升级 6.0

RxJS 升级

RxJS 6.0 已经发布了好几个月了,最近对项目中的 RxJS 进行了升级。

6.0 版本主要是在模块化方面做了改进。为了方便从 RxJS 5 过渡到 RxJS 6,RxJS 团队提供了 rxjs-compat 兼容包。本次升级直接安装 6.0 版本,因此不考虑向后兼容,如果有需要可以参考官方的升级文档

下面总结下升级过程需要注意的地方:

Import path

6.0 版本中,将模块重新划分为 5 类:

  • rxjs

    包含创建类方法、类型、调度器以及工具

  • rxjs/operators

    包含用于管道操作的操作符

  • rxjs/ajax

    包含 HTTP 请求相关的实现

  • rxjs/webSocket

    包含 WebSocket 相关的实现

  • rxjs/testing

    包含 RxJS 测试工具

如果项目中使用的是 TS,建议使用 rxjs-tslint 自动更新导入路径,同时还支持转成 pipeable 操作符。

Pipe syntax

5.5 版本就开始支持 pipeable method,6.0 版本移除了之前的 dot-chaining 写法。

1
2
3
4
5
6
7
8
9
10
// dot-chaining
Observable.fromEvent(document.getElementById('search-input'), 'input')
.debounceTime(500)
.pluck('target', 'value')
.map(v => v.toString().trim())
.filter(v => v !== '')
.distinctUntilChanged()
.subscribe(v => {
this._searchSubject.next(v);
});
1
2
3
4
5
6
7
8
// pipeable
fromEvent(document.getElementById('search-input'), 'input').pipe(
debounceTime(500),
pluck('target', 'value'),
map(v => v.toString().trim()),
filter(v => v !== ''),
distinctUntilChanged()
).subscribe(v => {});

两个版本代码使用方式上的更直观的对比,可以查看这篇文章

Rename operators

在管道操作符中,有些操作符名属于 JS 保留字,需要重命名。

  • do -> tap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // v6+
    import { of } from 'rxjs';
    import { tap, map } from 'rxjs/operators';

    const source = of(1, 2, 3, 4, 5);
    const example = source.pipe(
    tap(val => console.log(`BEFORE MAP: ${val}`)),
    map(val => val + 10),
    tap(val => console.log(`AFTER MAP: ${val}`))
    );

    // output: 11...12...13...14...15
    const subscribe = example.subscribe(val => console.log(val));
  • catch -> catchError

    1
    2
    3
    4
    5
    // v6+
    import { throwError, of } from 'rxjs';
    import { catchError } from 'rxjs/operators';

    throwError('Error').pipe(catchError(val => of(`caught error: ${val}`))).subscribe(val => console.log(val));
  • switch -> switchAll

    使用 switchMap

    1
    2
    3
    4
    5
    6
    import { switchMap } from './switchMap';
    import { identity } from '../util/identity';
    export function switchAll() {
    return switchMap(identity);
    }
    //# sourceMappingURL=switchAll.js.map
  • finally -> finalize

Deprecated methods

  • Observable.if -> iif()

    1
    2
    3
    4
    5
    Observable.if(test, a$, b$);

    // becomes

    iif(test, a$, b$);
  • Observable.error -> throwError()

    1
    2
    3
    4
    5
    Observable.throw(new Error());

    // becomes

    throwError(new Error());
  • merge

    1
    2
    3
    4
    5
    6
    7
    import { merge } from 'rxjs/operators';
    a$.pipe(merge(b$, c$));

    // becomes

    import { merge } from 'rxjs';
    merge(a$, b$, c$);
  • concat

    1
    2
    3
    4
    5
    6
    7
    import { concat } from 'rxjs/operators';
    a$.pipe(concat(b$, c$));

    // becomes

    import { concat } from 'rxjs';
    concat(a$, b$, c$);
  • combineLatest

    1
    2
    3
    4
    5
    6
    7
    import { combineLatest } from 'rxjs/operators';
    a$.pipe(combineLatest(b$, c$));

    // becomes

    import { combineLatest } from 'rxjs';
    combineLatest(a$, b$, c$);
  • race

    1
    2
    3
    4
    5
    6
    7
    import { race } from 'rxjs/operators';
    a$.pipe(race(b$, c$));

    // becomes

    import { race } from 'rxjs';
    race(a$, b$, c$);
  • zip

    1
    2
    3
    4
    5
    6
    7
    import { zip } from 'rxjs/operators';
    a$.pipe(zip(b$, c$));

    // becomes

    import { zip } from 'rxjs';
    zip(a$, b$, c$);

Migrate result selector

5.x 中有些操作符支持可选的 resultSelector 参数,6.x 版本中移除了该参数。

  • first()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // v5+
    source.pipe(
    first(predicate, resultSelector, defaultValue)
    )

    // v6+
    source.pipe(
    first(predicate, defaultValue),
    map(resultSelector)
    )

    source.pipe(
    map((v, i) => [v, i]),
    first(([v, i]) => predicate(v, i)),
    map(([v, i]) => resultSelector(v, i)),
    )
  • last()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // v5+
    source.pipe(
    last(predicate, resultSelector, defaultValue)
    )

    // v6+
    source.pipe(
    last(predicate, defaultValue),
    map(resultSelector)
    )

    source.pipe(
    map((v, i) => [v, i]),
    last(([v, i]) => predicate(v, i)),
    map(([v, i]) => resultSelector(v, i)),
    )
  • mergeMap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // v5+
    source.pipe(
    mergeMap(fn1, fn2, concurrency)
    )

    // v6+
    source.pipe(
    mergeMap((a, i) => fn1(a, i).pipe(
    map((b, ii) => fn2(a, b, i, ii))
    )),
    concurrency
    )
  • mergeMapTo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // v5+
    source.pipe(
    mergeMapTo(a$, resultSelector)
    )

    // v6+
    source.pipe(
    mergeMap((x, i) => a$.pipe(
    map((y, ii) => resultSelector(x, y, i, ii))
    )
    )
    )
  • concatMap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // v5+
    source.pipe(
    concatMap(fn1, fn2)
    )

    // v6+
    source.pipe(
    concatMap((a, i) => fn1(a, i).pipe(
    map((b, ii) => fn2(a, b, i, ii))
    ))
    )
  • concatMapTo()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // v5+
    source.pipe(
    concatMapTo(a$, resultSelector)
    )

    // v6+
    source.pipe(
    concatMap((x, i) => a$.pipe(
    map((y, ii) => resultSelector(x, y, i, ii))
    ))
    )
  • switchMap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // v5+
    source.pipe(
    switchMap(fn1, fn2)
    )

    // v6+
    source.pipe(
    switchMap((a, i) => fn1(a, i).pipe(
    map((b, ii) => fn2(a, b, i, ii))
    ))
    )
  • switchMapTo()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // v5+
    source.pipe(
    switchMapTo(a$, resultSelector)
    )

    // v6+
    source.pipe(
    switchMap((x, i) => a$.pipe(
    map((y, ii) => resultSelector(x, y, i, ii))
    ))
    )
  • exhaustMap()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // v5+
    source.pipe(
    exhaustMap(fn1, fn2)
    )

    // v6+
    source.pipe(
    exhaustMap((a, i) => fn1(a, i).pipe(
    map((b, ii) => fn2(a, b, i, ii))
    ))
    )
  • forkJoin()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // v5+
    forkJoin(a$, b$, c$, resultSelector)
    forkJoin([a$, b$, c$], resultSelector)

    // v6+
    forkJoin(a$, b$, c$).pipe(
    map(x => resultSelector(...x))
    )

    // or
    forkJoin([a$, b$, c$]).pipe(
    map(x => resultSelector(...x))
    )
  • zip()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // v5+
    zip(a$, b$, c$, resultSelector)
    zip([a$, b$, c$], resultSelector)

    // v6+
    zip(a$, b$, c$).pipe(
    map(x => resultSelector(...x))
    )

    // or
    zip([a$, b$, c$]).pipe(
    map(x => resultSelector(...x))
    )
  • combineLatest()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // v5+
    combineLatest(a$, b$, c$, resultSelector)
    combineLatest([a$, b$, c$], resultSelector)

    // v6+
    combineLatest(a$, b$, c$).pipe(
    map(x => resultSelector(...x))
    )
    // or
    combineLatest([a$, b$, c$]).pipe(
    map(x => resultSelector(...x))
    )
  • fromEvent()

    1
    2
    3
    4
    5
    6
    7
    // v5+
    fromEvent(button, 'click', resultSelector)

    // v6+
    fromEvent(button, 'click').pipe(
    map(resultSelector)
    )

参考