RxJS v5.x至v6更新指南

2020-10-09 15:01 更新

RxJS v6 到了! 盡管這是主要的版本更改(從 5.x 更改為 6.x), 我們已經(jīng)進(jìn)行了大量工作,以將艱巨的更改保持在最低限度。 在大多數(shù)情況下,這允許應(yīng)用程序和庫開發(fā)人員逐步更新 并使用 RxJS v6,無需對(duì)其代碼進(jìn)行任何修改。

向后兼容層簡化了更新過程,使您可以保持 應(yīng)用程序在您按自己的進(jìn)度處理大多數(shù)代碼更改時(shí)仍在運(yùn)行。 整個(gè)過程可以分階段進(jìn)行:

  1. 更新到最新版本的 RxJS 5.5,并確保您已解決由錯(cuò)誤修復(fù)引起的所有問題。

  1. 安裝 RxJS v6和 向后兼容軟件包 rxjs-compat

  1. 如果您的應(yīng)用受到 的一些 重大更改 未涵蓋 rxjs-compat的影響,請(qǐng)根據(jù)下面提供的說明更新受影響的代碼。

  1. 最終,您將需要 刪除兼容性層以完成對(duì) RxJS v6 的更新。 這樣做將大大減小應(yīng)用程序的大小。

要重構(gòu) TypeScript 代碼,使其不依賴于 rxjs-compat,可以使用 rxjs-tslint。

npm i -g rxjs-tslint
rxjs-5-to-6-migrate -p [path/to/tsconfig.json]

  1. 在 RxJS v7 發(fā)行版之前,您將需要?jiǎng)h除和替換所有 不推薦使用的功能 。

向后兼容

為了最大程度地減少升級(jí)的影響,RxJS v6 發(fā)行了一個(gè)同級(jí)軟件包 軟件包 rxjs-compat,該 在 v6 和 v5 API 之間提供了一個(gè)兼容性層。 擁有現(xiàn)有應(yīng)用程序的大多數(shù)開發(fā)人員都應(yīng)同時(shí)安裝 來升級(jí) rxjsrxjs-compat^ 6.0.0 ^ 6.0.0 :

npm install rxjs@6 rxjs-compat@6

有關(guān)此軟件包的詳細(xì)信息,請(qǐng)參見此處 .

兼容性軟件包會(huì)增加應(yīng)用程序的捆綁包大小,這就是為什么我們建議您在應(yīng)用程序和依賴項(xiàng)更新后立即將其刪除。 如果您使用的 Webpack 版本是 4.0.0之前,則此大小增加會(huì)加劇。

有關(guān)要?jiǎng)h除的更新內(nèi)容的完整說明 rxjs-compat,請(qǐng)參閱下文 刪除兼容性層 。 還要注意,將應(yīng)用程序完全更新到 v6 可能會(huì)暴露以前未顯示的現(xiàn)有類型錯(cuò)誤。

rxjs-compat 未涵蓋的重大更改

如果已安裝 rxjs-compat,則可能只需要立即解決兩個(gè)重大更改。

同步錯(cuò)誤處理

同步錯(cuò)誤處理( 放置對(duì) 的調(diào)用 Observable.subscribe()在 方法 try/catch不再支持 塊中 )。 如果使用了它,則必須使用 的 將其替換為異步錯(cuò)誤處理 error回調(diào) Observable.subscribe()方法中 。

TypeScript 原型運(yùn)算符

如果要在 TypeScript 中定義自己的原型運(yùn)算符并修改 Observable名稱空間,則需要更改運(yùn)算符代碼才能編譯 TypeScript。 。 這是一種相對(duì)罕見的情況,可能只影響高級(jí) TypeScript 開發(fā)人員。

取代同步錯(cuò)誤處理 以下示例顯示了在 內(nèi)預(yù)訂可觀察對(duì)象的代碼, try/catch塊 以便同步處理錯(cuò)誤:

try {
  source$.subscribe(nextFn, undefined, completeFn);
} catch (err) {
  handleError(err);
}

以下代碼通過定義以下內(nèi)容的錯(cuò)誤回調(diào)將其更新為異步處理錯(cuò)誤 Observable.subscribe()

source$.subscribe(nextFn, handleError, completeFn);

下一個(gè)示例顯示了一個(gè)依賴于同步錯(cuò)誤處理的測試:

it('should emit an error on subscription', () => {
  expect(source$.subscribe()).toThrow(Error, 'some message');
});

以下代碼顯示了如何更正測試以使用異步錯(cuò)誤處理:

it('should emit an error on subscription', (done) => {
  source$.subscribe({
    error(err) {
      expect(err.message).toEqual('some message');
    }
  });
});

TypeScript用戶定義的原型運(yùn)算符

以下示例顯示了您需要在用戶定義的原型運(yùn)算符中進(jìn)行的更改類型,以便正確編譯TypeScript。

這是一個(gè)用戶定義的原型運(yùn)算符的示例r:

Observable.prototype.userDefined = function () {
  return new Observable((subscriber) => {
    this.subscribe({
      next(value) { subscriber.next(value); },
      error(err) { subscriber.error(err); },
      complete() { subscriber.complete(); },
   });
  });
};


source$.userDefined().subscribe();

要使此代碼在 v6 中正確編譯,請(qǐng)按如下所示進(jìn)行更改:

const userDefined = <T>() => (source: Observable<T>) => new Observable<T>((subscriber) => {
    source.subscribe({
      next(value) { subscriber.next(value); },
      error(err) { subscriber.error(err); },
      complete() { subscriber.complete(); },
   });
  });
});


source$.pipe(
  userDefined(),
)
.subscribe();

刪除兼容性層

如果使用從 v6 中刪除但 支持的功能,則 rxjs-compat軟件包 必須重構(gòu)或重寫代碼以完成對(duì) v6 的更新。 以下功能區(qū)域取決于兼容性層:

  • 導(dǎo)入路徑已更改。
  • 操作員語法已更改為使用管道而不是鏈接。
  • 對(duì)可觀察對(duì)象進(jìn)行操作的類已被函數(shù)替換。
  • 在具有 resultSelector 參數(shù)的函數(shù)中,大多數(shù)情況下已棄用該參數(shù),而對(duì)于兩個(gè)函數(shù)則將其刪除。 必須先更新已刪除的內(nèi)容,然后才能刪除兼容性層。

導(dǎo)入路徑

如果您是 TypeScript 開發(fā)人員,建議您使用它 rxjs-tslint來重構(gòu)導(dǎo)入路徑。

對(duì)于 JavaScript 開發(fā)人員,一般規(guī)則如下:

  1. rxjs: 創(chuàng)建方法,類型,調(diào)度程序和實(shí)用程序

import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent, SubscriptionLike, PartialObserver } from 'rxjs';

  1. rxjs / operators :所有可管道運(yùn)算符:

import { map, filter, scan } from 'rxjs/operators';

  1. rxjs / webSocket: Web 套接字主題實(shí)現(xiàn)

import { webSocket } from 'rxjs/webSocket';

  1. rxjs / ajax :Rx ajax 實(shí)現(xiàn)

import { ajax } from 'rxjs/ajax';    

  1. rxjs / testing :測試實(shí)用程序

import { TestScheduler } from 'rxjs/testing';    

操作符管道語法

鏈接運(yùn)算符的以前編碼風(fēng)格已被一個(gè)運(yùn)算符的結(jié)果傳遞給另一個(gè)運(yùn)算符的結(jié)果所取代。 5.5版中添加了可管道運(yùn)算符。

在刪除兼容性層之前,必須重構(gòu)代碼以僅使用可管道運(yùn)算符。 對(duì)于 Typescript,該 tslint工具通過將轉(zhuǎn)換應(yīng)用于類型正確的代碼來在某種程度上實(shí)現(xiàn)流程自動(dòng)化。

可觀察的類

所有可觀察的類,以支持執(zhí)行與類方法相同的操作的現(xiàn)有或新運(yùn)算符。 例如, ArrayObservable.create(myArray)可以由 替換 from(myArray)或 為new運(yùn)算符 fromArray()。

  • ConnectableObservable從直接利用隱藏在 V6 和只能通過運(yùn)營商進(jìn)行訪問 multicast, publishpublishReplay,和 publishLast
  • SubscribeOnObservable在 v6 中無法直接使用,并且只能通過operator進(jìn)行訪問 subscribeOn。
v6創(chuàng)建功能 v5類
from ArrayLikeObservable
of 數(shù)組可觀察
bindCallback BoundCallbackObservable
bindNodeCallback BoundNodeCallbackObservable
defer 延遲可觀察
empty or EMPTY (constant) 空可觀察
throwError 錯(cuò)誤可觀察
forkJoin 前叉可觀察
fromEvent FromEventObservable
fromEventPattern FromEventPatternObservable
from FromObservable
generate GenerateObservable
iif IfObservable
interval 間隔可觀察
from IteratorObservable
NEVER (constant) 永不觀察
pairs 可觀察對(duì)
from 承諾可觀察
range 可觀察范圍
of 標(biāo)量可觀察
timer 計(jì)時(shí)器可觀察
using 使用可觀察

結(jié)果選擇器已刪除或已棄用

結(jié)果選擇器是一個(gè)很少有人使用的功能(在許多情況下沒有記錄),但是卻在代碼庫中增加了很大的膨脹。 如果使用它們,則需要 替換不連續(xù)的 resultSelector用外部結(jié)果選擇代碼 參數(shù)。

  • resultSelector對(duì)參數(shù) first()last()刪除 的 V6 發(fā)動(dòng)機(jī)。 如果使用了這些,則必須將代碼更新為在沒有 情況下運(yùn)行 rxjs-compat軟件包的 。
  • resultSelector已 了許多映射運(yùn)算符可用 參數(shù) 棄用 v6 ,并將實(shí)現(xiàn)重寫為小得多。 它們將在沒有兼容軟件包的情況下繼續(xù)工作,但是必須在 v7 版本之前將其更換。

棄用

在 RxJS 發(fā)行 v7 之前,您將需要?jiǎng)h除和替換所有不贊成使用的功能。 以下區(qū)域包含不推薦使用的功能:

  • Observable.ifObservable.throw 這些方法已由 static 代替 iif()throwError()function 。 使用 rxjs-tslint 將方法調(diào)用與函數(shù)調(diào)用轉(zhuǎn)換。
  • “創(chuàng)造”運(yùn)營商 下面的運(yùn)營商已經(jīng)從移動(dòng) rxjs/operatorsrxjs中,其使用已經(jīng)改變:
  • merge
  • concat
  • combineLatest
  • race
  • zip
  • 結(jié)果選擇器

如何:轉(zhuǎn)換為管道語法

在將點(diǎn)鏈運(yùn)算符轉(zhuǎn)換為可管道運(yùn)算符之前,請(qǐng)確保從中導(dǎo)入所有使用的運(yùn)算符 rxjs/operators。 例如:

import { map, filter, catchError, mergeMap } from 'rxjs/operators';

更改了以下運(yùn)算符名稱,因?yàn)樗鼈兊狞c(diǎn)鏈名稱是JavaScript中的保留字:

  • do-> tap
  • catch-> catchError
  • switch-> switchAll
  • finally-> finalize

要將點(diǎn)鏈運(yùn)算符轉(zhuǎn)換為可管道運(yùn)算符,請(qǐng)將所有運(yùn)算符 包裝在 符 pipe()從可觀察的源中 方法中,刪除點(diǎn),然后添加逗號(hào)以將每個(gè)運(yùn)算 傳遞 pipe()作為參數(shù) 。

例如,以下代碼使用鏈接:

source
 .map(x => x + x)
 .mergeMap(n => of(n + 1, n + 2)
   .filter(x => x % 1 == 0)
   .scan((acc, x) => acc + x, 0)
 )
 .catch(err => of('error found'))
 .subscribe(printResult);

轉(zhuǎn)換為管道:

source.pipe(
 map(x => x + x),
 mergeMap(n => of(n + 1, n + 2).pipe(
   filter(x => x % 1 == 0),
   scan((acc, x) => acc + x, 0),
 )),
 catchError(err => of('error found')),
).subscribe(printResult);

如何:轉(zhuǎn)換不建議使用的方法

Observable.if> iif()

Observable.if(test, a$, b$);


// becomes


iif(test, a$, b$);

Observable.error> throwError()

Observable.throw(new Error());


// becomes


throwError(new Error());

import { merge } from 'rxjs/operators';
a$.pipe(merge(b$, c$));


// becomes


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

康卡特

import { merge } from 'rxjs/operators';
a$.pipe(merge(b$, c$));


// becomes


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

結(jié)合最新

import { combineLatest } from 'rxjs/operators';
a$.pipe(combineLatest(b$, c$));


// becomes


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

種族

import { race } from 'rxjs/operators';
a$.pipe(race(b$, c$));


// becomes


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

zip

import { zip } from 'rxjs/operators';
a$.pipe(zip(b$, c$));


// becomes


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

如何:結(jié)果選擇器遷移

在 RxJS v5.x 中,許多運(yùn)算符都有一個(gè)可選的 resultSelector 參數(shù),您可以在其中傳遞用于處理運(yùn)算結(jié)果的函數(shù)。

如果使用參數(shù),則必須通過將結(jié)果選擇功能移出原始操作員調(diào)用并將其應(yīng)用于調(diào)用結(jié)果的方式來更新代碼。

  • 該參數(shù) 已從 刪除 v6 中的first()和last()運(yùn)算符中 ,但 rxjs-compat 軟件包支持該參數(shù)。 您必須更新代碼才能刪除兼容性軟件包。
  • 該參數(shù) 已棄用 在以下運(yùn)算符中 ,并將在 v7 中刪除。 您必須先更新代碼,然后才能遷移到 v7。
    • mergeMap()
    • mergeMapTo()
    • concatMap()
    • concatMapTo()
    • switchMap
    • switchMapTo()
    • exhaustMap()
    • forkJoin()
    • 壓縮()
    • CombineLatest()
    • fromEvent()

第一()

  • 與 resultSelector(v5.x)

source.pipe(
 first(predicate, resultSelector, defaultValue)
)

  • 沒有 resultSelector(如果您不使用其中的索引):

source.pipe(
 first(predicate, defaultValue),
 map(resultSelector)
)

  • 沒有 resultSelector(如果您正在使用其中的索引)

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

持續(xù)()

  • 與 resultSelector(v5.x)

source.pipe(
 last(predicate, resultSelector, defaultValue)
)

  • 沒有 resultSelector(如果您不使用其中的索引):

source.pipe(
 last(predicate, defaultValue),
 map(resultSelector)
)

  • 沒有 resultSelector(如果您正在使用其中的索引)

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

mergeMap()

  • 與 resultSelector(v5.x)一起使用 注意:concurrency-limit 參數(shù)是可選的,為完整起見在此顯示。

source.pipe(
 mergeMap(fn1, fn2, concurrency)
)

  • 內(nèi)部地圖實(shí)現(xiàn)了沒有 resultSelector 的相同功能。

source.pipe(
 mergeMap((a, i) => fn1(a, i).pipe(
   map((b, ii) => fn2(a, b, i, ii))
 )),
 concurrency
)

mergeMapTo()

  • 與 resultSelector(v5.x)

source.pipe(
 mergeMapTo(a$, resultSelector)
)

  • 沒有 resultSelector

source.pipe(
 mergeMap((x, i) => a$.pipe(
   map((y, ii) => resultSelector(x, y, i, ii))
 )
)

concatMap()

  • 與 resultSelector (v5.x)

source.pipe(
 concatMap(fn1, fn2)
)

  • 內(nèi)部地圖實(shí)現(xiàn)了沒有 resultSelector 的相同功能:

source.pipe(
 concatMap((a, i) => fn1(a, i).pipe(
   map((b, ii) => fn2(a, b, i, ii))
 )
)

concatMapTo()

  • 與 resultSelector(v5.x)

source.pipe(
 concatMapTo(a$, resultSelector)
)

  • 沒有 resultSelector

source.pipe(
 concatMap((x, i) => a$.pipe(
   map((y, ii) => resultSelector(x, y, i, ii))
 )
)

switchMap()

  • 與 resultSelector(v5.x)

source.pipe(
 switchMap((a, i) => fn1(a, i).pipe(
   map((b, ii) => fn2(a, b, i, ii))
 )
)

  • 內(nèi)部地圖實(shí)現(xiàn)了沒有 resultSelector 的相同功能

source.pipe(
 switchMap((a, i) => fn1(a, i).pipe(
   map((b, ii) => fn2(a, b, i, ii))
 )
)

switchMapTo()

  • 與 resultSelector(v5.x)

source.pipe(
 switchMapTo(a$, resultSelector)
)

  • 沒有 resultSelector

source.pipe(
 switchMap((x, i) => a$.pipe(
   map((y, ii) => resultSelector(x, y, i, ii))
 )
)

exhaustMap()

  • 與 resultSelector(v5.x)

source.pipe(
 exhaustMap(fn1, fn2)
)

  • 內(nèi)部地圖實(shí)現(xiàn)了沒有 resultSelecto 的相同功能

source.pipe(
 exhaustMap((a, i) => fn1(a, i).pipe(
   map((b, ii) => fn2(a, b, i, ii))
 )
)

forkJoin()

  • 與 resultSelector(v5.x)

forkJoin(a$, b$, c$, resultSelector)
// or
forkJoin([a$, b$, c$], resultSelector)

  • 沒有 resultSelector

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

壓縮()

  • 與 resultSelector(v5.x)

zip(a$, b$, c$, resultSelector)
// or
zip([a$, b$, c$], resultSelector)

  • 沒有 resultSelector

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

combineLatest()

  • 與 resultSelector(v5.x)

combineLatest(a$, b$, c$, resultSelector)
// or
combineLatest([a$, b$, c$], resultSelector)

  • 沒有 resultSelector

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

fromEvent()

  • 與resultSelector(v5.x)

fromEvent(button, 'click', resultSelector)

  • 沒有 resultSelector

fromEvent(button, 'click').pipe(
 map(resultSelector)
)

UMD 模塊名稱更改

在 RxJS v6.x 中,UMD 模塊名稱已從Rx更改為 rxjs,以便與其他導(dǎo)入模塊名稱對(duì)齊。

const rx= Rx;


rx.Observable.of(1,2,3).map(x => x + '!!!');


// becomes


const { of } = rxjs;
const { map } = rxjs.operators;


of(1,2,3).pipe(map(x => x + '!!!')); // etc
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)