從Angular6開始入門RxJS6

簡(jiǎn)介

本文的讀者受眾

  • 正準(zhǔn)備學(xué)習(xí)Angular的人
  • 想要知道Rx和RxJS相關(guān)知識(shí)的人

這篇文章是什么处铛?

Angular使用RxJS標(biāo)準(zhǔn)庫(kù)來(lái)有效地實(shí)現(xiàn)異步處理。
為了使用好 RxJS,需要考慮到與傳統(tǒng)編程的不同之處俊抵。
就我而言,在什么都不知道的狀態(tài)下閱讀官方文檔,我也不明白它的優(yōu)勢(shì)或具體用法锌订。
不能理解的最大因素是我并沒有形成RX的概念印象眨业。
如果我從一開始就擁有這個(gè)概念印象急膀,我認(rèn)為我的學(xué)習(xí)會(huì)更順利...... orz

所以在這篇文章中

  • 我想現(xiàn)在開始使用Angular 6,但我還需要學(xué)習(xí)一個(gè)名為RxJS的庫(kù)...
  • 我想學(xué)習(xí)Rx龄捡,但我不知道從哪里開始
  • 我想知道Rx是什么

將以這些方向卓嫂,對(duì)RxJS使用的優(yōu)點(diǎn)和他的概念、經(jīng)常使用的方法進(jìn)行解釋聘殖。

RxJS

反應(yīng)式擴(kuò)展
Reactive Extensions(Rx)是晨雳,一個(gè)使用可觀察數(shù)據(jù)序列和類LINQ樣式的請(qǐng)求運(yùn)算符,來(lái)創(chuàng)建(實(shí)現(xiàn))異步及基于事件程序的技術(shù)庫(kù)奸腺。

數(shù)據(jù)序列餐禁,擁有各種形式的存在。例如來(lái)自文件和Web服務(wù)的數(shù)據(jù)流突照,對(duì)Web服務(wù)的請(qǐng)求帮非,系統(tǒng)通知以及用戶操作的事件。

https://msdn.microsoft.com/en-us/library/hh242985(v=vs.103).aspx

  • 在簡(jiǎn)單閱讀完上面鏈接內(nèi)容后,繼續(xù)以下內(nèi)容

在Angular中使用RxJS的優(yōu)點(diǎn)

Rx的世界中末盔,處理的值并非固定筑舅,而是可以不斷變化的數(shù)據(jù)流。
您可以在流中放置任何內(nèi)容庄岖,例如用戶操作的事件值活A(yù)PI響應(yīng)結(jié)果等異步值豁翎,或數(shù)字和字符串等同步值。

任何值都可以在數(shù)據(jù)流中流入隅忿,Rx提供的通用格式進(jìn)行數(shù)據(jù)的加工和時(shí)機(jī)的處理心剥。

無(wú)論是事件還是API響應(yīng),您都可以使用相同的格式并通過便捷的代碼段編寫外觀漂亮的代碼背桐。
這是使用Rx的最大好處优烧。
因?yàn)镴avaScript缺少用于操作數(shù)組和對(duì)象的標(biāo)準(zhǔn)API,所以我認(rèn)為有很多機(jī)會(huì)使用名為lodash的庫(kù)链峭。
我認(rèn)為Rx是Promise版的 lodash畦娄。


我稱之為“時(shí)序處理”的是具體的以下實(shí)現(xiàn)。

  • 控制高速連續(xù)的事件在每50ms發(fā)生
    • 控制經(jīng)常出現(xiàn)瀏覽器滾動(dòng)控件
  • 最后一次檢測(cè)到事件后100毫秒
    • 頻繁出現(xiàn)的表單相關(guān)處理
  • 事件在一定時(shí)間內(nèi)發(fā)生過多次
    • 控制雙擊等

在Angular ( SPA )中弊仪,時(shí)序相關(guān)的處理是很頻繁的熙卡,如果每次都使用標(biāo)準(zhǔn)的 setTimeout 等方法實(shí)現(xiàn)的話,會(huì)有很多性能的浪費(fèi)励饵,而且代碼也會(huì)變得極其難以閱讀驳癌、理解。
通過使用RxJS,您可以將所有數(shù)據(jù)的合并役听、過濾颓鲜、映射與時(shí)間軸的處理,輕松地放在一起實(shí)現(xiàn)典予。

Rx的印象

理解事物最重要的是印象的轉(zhuǎn)換甜滨。
在本章中,我們將之前所羅列的Rx概念升華為印象瘤袖。

這次我為那些根本不了解Rx的人做了一個(gè)簡(jiǎn)單的故事衣摩。
通過跟蹤這個(gè)故事,我認(rèn)為您可以學(xué)習(xí)到Rx的粗略概念捂敌。


  • 在夏季時(shí)艾扮,有一條流淌桃子的河流( stream )
    • 當(dāng)然,除了桃子 ( value ) 以外也有魚 ( value )在流動(dòng)黍匾。
    • 在秋栏渺、冬、春時(shí)不會(huì)流淌桃子
  • 您想利用這條神秘的河流锐涯,制造出材料成本為0的桃子罐頭磕诊,然后販賣賺大錢。
  • 因此,我們需要建立一個(gè)系統(tǒng)霎终,可以自動(dòng)從河流中收集 ( filter )桃子滞磺、將其轉(zhuǎn)換成桃子罐頭 ( map )
    • 系統(tǒng)的運(yùn)行需要消耗電力
  • 夏天的時(shí)候運(yùn)行系統(tǒng) ( subscribe )
  • 夏天以外的時(shí)候不會(huì)有桃子,所以要關(guān)閉系統(tǒng)( unsubscribe )莱褒,以便他不消耗不必要的電力

Rx最重要的概念為“流”击困,所以經(jīng)常用河流做比較。
現(xiàn)實(shí)中的河流广凸,在沒有我們做任何事情的情況下也會(huì)繼續(xù)自由地流動(dòng)阅茶。
像上面的故事中的一樣,管理者打開裝載的開關(guān)谅海,監(jiān)視 ( subscribe ) 河流脸哀、在罐頭制造裝置 ( operators ) 接收桃子( value ) 時(shí),執(zhí)行期望的處理扭吁。

Rx初學(xué)者剛開始經(jīng)常遇到的問題是撞蜂,因?yàn)橥浭褂胹ubscribe,而一直在查找值不流出的原因侥袜。
但是蝌诡,如果您從一開始就能想到故事,就不會(huì)把時(shí)間浪費(fèi)在這種類似的錯(cuò)誤上枫吧。

此外浦旱,故事中出現(xiàn)的電力,就現(xiàn)實(shí)而言也就是客戶端的CPU資源由蘑。與每個(gè)月發(fā)生的電費(fèi)不同闽寡,內(nèi)存泄漏經(jīng)常出現(xiàn)在債務(wù)堆積的情況下代兵。
因此尼酿,當(dāng)您銷毀組件時(shí),請(qǐng)務(wù)必記住取消訂閱內(nèi)部訂閱的流植影。

最后裳擎,將此故事轉(zhuǎn)換為實(shí)際代碼如下所示:

private subscription: Subscription;

ngOnInit() {
    this.subscription = of('桃子', '鯉魚').pipe(
        filter(v => v === '桃子'), 
        map(v => v + '罐頭')
    ).subscribe(console.log);
}

ngOnDestroy() {
    this.subscription.unsubscribe();
}
桃子罐頭

RxJS的概念總結(jié)

我認(rèn)為可以在上一章的故事中大致理解Rx的概念,但我將再次回到原文思币。

在官方指南中鹿响,Rx庫(kù)由以下公式表示:

Rx = Observables + LINQ (Operators) + Schedulers

正如我前面提到的,Observables是河流谷饿,事件惶我,異步處理等的可觀察對(duì)象,也是流的起點(diǎn)博投。
此外绸贡,Operators 可以被視為決定如何處理流中流動(dòng)值的設(shè)備。
一旦訂閱后,您可以使用反應(yīng)式編程執(zhí)行一系列流處理听怕。

Subject

Subject類經(jīng)常以各種方式使用捧挺,例如在Rx的邏輯中通知或臨時(shí)存儲(chǔ)值。

Subject結(jié)合上面的例子來(lái)說(shuō)尿瞭,類似于大壩闽烙。
大壩連接到河流,可以觀察從大壩流出的價(jià)值声搁,另外黑竞,也可以從外部設(shè)定值。
他就像流版的變量一樣疏旨。

Subject有很多種類型摊溶,所以不能一概而論,但我認(rèn)為它的概念印象是下面的圖像充石。

重復(fù)出現(xiàn)的 Observable 和 Operators

如果上面說(shuō)的已經(jīng)全部理解的話莫换,之后再有什么樣的川 ( Observable ) 、什么樣的裝置( Operators ) 骤铃、剩下的工作僅僅是記住它們罷了拉岁。

這一次,我試圖整理一個(gè)簡(jiǎn)單的使用場(chǎng)景惰爬,專注于我經(jīng)常使用的東西喊暖。
此列表優(yōu)先考慮具體的用途和印象,因?yàn)槿绻總€(gè)文本中都包含詳細(xì)說(shuō)明撕瞧,則文字?jǐn)?shù)量將是巨大的陵叽。
有關(guān)詳細(xì)用法,請(qǐng)參閱官方文檔丛版。

Observable

from

  • 將Promise 或者 iterator 的值 ( string巩掺、array 等 ) 轉(zhuǎn)換為 Observable

使用例:處理API響應(yīng)結(jié)果并檢索所需的值

from(
  fetch('https://jsonplaceholder.typicode.com/posts/1')
    .then(r => r.json())
).map(v => v. userId).subscribe(console.log);

// 1

fromEvent

  • 將event轉(zhuǎn)換為 Observable

使用例:雙擊的捕捉

const click$ = fromEvent(document, 'click');

click$.subscribe(console.log);

merge

  • 合并流動(dòng)的值

使用場(chǎng)景:將各種事件合成為一個(gè)觸發(fā)器

const click$ = fromEvent(targetElement, 'click');
const mouseover$ = fromEvent(targetElement, 'mouseover');

merge(click$, mouseover$).subscribe(() => {
  // 期望的處理
});

of

  • 將值轉(zhuǎn)換為 Observable

使用場(chǎng)景:測(cè)試、確認(rèn)用页畦、流分裂或結(jié)合時(shí)的搭配

const hoge$ = of(1, 2, 3);
const huga$ = fromEvent(document, 'click');

merge(hoge$, huga$).subscribe(console.log);

interval

  • 定時(shí)流動(dòng)的值

使用場(chǎng)景:顯示已用時(shí)間

this.count$ = interval(1000);
// wait 1sec
// 0
// wait 1sec
// 1
// wait 1sec
// 2
// ...
<div>count: {{ count$ | async }}</div>

concat

  • 保存流的順序并結(jié)合

使用場(chǎng)景:在應(yīng)用中保存緩存內(nèi)容和API響應(yīng)的合成胖替,并立即顯示緩存 → 切換到準(zhǔn)確的數(shù)據(jù)

this.article$ = concat(this.store.select(getSelectArticle), this.articleDb.findByKey(articleKey));

Operators

tap ( 舊 do )

  • 在不影響流的情況下進(jìn)行任何處理

使用場(chǎng)景:日志顯示

stream$
    .pipe(
        tap(console.log), 
        tap(console.warn), 
        tap(console.error),
    )
    .subscribe();

map / pluck

  • 流的値的加工?轉(zhuǎn)換?抽出

使用場(chǎng)景:處理API響應(yīng)結(jié)果(抽出需要的值)

const apiResponse$ = of({ userId: 1, body: 'hoge huga piyo' });

const userId$ = apiResponse$.pipe(map(v => v.userId));
// ---------------------------------------------------
// 如果只想要取得值,可以使用pluck豫缨,讓代碼更簡(jiǎn)潔
const userId$ = apiResponse$.pipe(pluck('userId'));

filter

  • 過濾值

使用場(chǎng)景:重定向事件時(shí)顯示加載進(jìn)度條

const routerEvent$ = this.router.events;
routerEvent$
    .pipe(filter(e => e instanceof NavigationStart))
    .subscribe(() => this.store.dispatch(new ShowLoadingSpinnerAction()));

skip

  • 跳過值

使用場(chǎng)景:跳過組件生成后聯(lián)動(dòng)處理

// 跳過第一次流動(dòng)的值独令,因?yàn)樗皇怯脩舨僮鞲牡闹?this.route.params.pipe(pluck('categoryId'), skip(1)).subscribe(categoryId => {
    console.log(`changed categoryId: ${ categoryId }`);
});

scan

  • 使用以前的值

使用場(chǎng)景:無(wú)限滾動(dòng)條的項(xiàng)目列表管理

this.items$ = nextItemSubject$.scan((acc, curr) => {
    return acc.concat(curr);
}, []);

take

  • 確定值流動(dòng)的次數(shù)

使用場(chǎng)景:只使用變動(dòng)值的最初 x 回

// 如果不使用take(1) 的話,每回store值更新的時(shí)候,都會(huì)調(diào)用API
this.store.select(getUserId).pipe(
    take(1), 
    concatMap(userId => this.apiService.get(userId))
).subscribe();

startWith

  • 指定最初流動(dòng)的值

使用場(chǎng)景:顯示經(jīng)過的時(shí)間(改良版)

※ 如果僅使用 interval 好芭,則第一秒將不會(huì)顯示任何內(nèi)容燃箭。

this.count$ = interval(1000).pipe(map(v => v + 1), startWith(0));
// 0
// wait 1sec
// 1
// wait 1sec
// 2
// ...
<div>count: {{ count$ | async }}</div>

takeUntil

  • 值流動(dòng)時(shí)的暫停處理

使用場(chǎng)景:在銷毀組件時(shí)通過Subject發(fā)送結(jié)束流的通知

※ 但請(qǐng)注意、這篇文章 介紹的內(nèi)存泄露

private onDestroy$ = new Subject();

ngOnInit() {
    interval(1000).pipe(takeUntil(this.onDestroy$)).subscribe(console.log);
}

ngOnDestroy() {
    this.onDestroy$.next();
}

concatMap

  • 將值轉(zhuǎn)變成Observable 后合并(執(zhí)行中的處理結(jié)束后轉(zhuǎn)到下一個(gè)操作處理)

使用場(chǎng)景:使用API1響應(yīng)結(jié)果調(diào)用API2

switchMap

  • 將值轉(zhuǎn)變成Observable 后合并(下一個(gè)値過來(lái)時(shí)舍败,中斷正在執(zhí)行的處理)

使用場(chǎng)景:實(shí)現(xiàn) auto complete 功能

debounceTime

  • 棄掉在兩次輸出之間小于指定時(shí)間的發(fā)出值

使用場(chǎng)景:實(shí)現(xiàn) auto complete 功能

this.autoCompleteList$ = this.form.valueChanges.pipe(
    debounceTime(100),
    switchMap(input => this.apiService.get(input)),
);

throttleTime

  • 控制值流動(dòng)的速度

使用場(chǎng)景:控制滾動(dòng)條事件

fromEvent(window, 'scroll').pipe(throttleTime(50)).subscribe(console.log);

withLatestFrom

  • 與合并后流最新的值進(jìn)行合并

使用場(chǎng)景:將點(diǎn)擊的用戶ID作為GA事件發(fā)送

fromEvent(targetElement, 'click').pipe(
    withLatestFrom(this.store.select(getUserId))
).subscribe(([_, userId]) => {
    this.analyticsService.sendEvent({ category: 'test', action: 'click', userId });
})

combineLatest

  • 如果對(duì)主流和合成流都進(jìn)行了更改招狸,則會(huì)發(fā)送每個(gè)流的最新值

使用場(chǎng)景:表單輸入值和store信息的合并

this.form.valueChanges.pipe(
    combineLatest(this.store.select(getUserId))
).subscribe(([input, userId]) => {
    console.log(`用戶userId( ${ userId } ) 輸入${ input } 中..`);
});
image

publish, share, refCount...etc

  • cold流轉(zhuǎn)化為hot流

cold / hot 的概念在這篇文章 中介紹

使用場(chǎng)景:使用API1響應(yīng)結(jié)果并行執(zhí)行API2和API3

※ 將 API1 流 hot 化后碗硬,可防止多次調(diào)用API1

const userId$ = from(fetch('https://jsonplaceholder.typicode.com/posts/1').then(r => r.json())).pipe(
    pluck('userId'), 
    publishReplay(1), 
    refCount()
);

userId$.concatMap(userId => this.api2Service.get(userId)).subscribe(console.log);
userId$.concatMap(userId => this.api3Service.get(userId)).subscribe(console.log);

關(guān)于RxJS6的導(dǎo)入

Observable、Subject 以及 Subscription

import { Observable, concat, Subject, Subscription } from 'rxjs';

Operators

import { map, tap } from 'rxjs/operators';

結(jié)束

image

翻譯自

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓢颅,一起剝皮案震驚了整個(gè)濱河市恩尾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挽懦,老刑警劉巖翰意,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異信柿,居然都是意外死亡冀偶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門渔嚷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)进鸠,“玉大人,你說(shuō)我怎么就攤上這事形病】湍辏” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵漠吻,是天一觀的道長(zhǎng)量瓜。 經(jīng)常有香客問我,道長(zhǎng)途乃,這世上最難降的妖魔是什么绍傲? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮耍共,結(jié)果婚禮上烫饼,老公的妹妹穿的比我還像新娘。我一直安慰自己试读,他們只是感情好杠纵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹏往,像睡著了一般淡诗。 火紅的嫁衣襯著肌膚如雪骇塘。 梳的紋絲不亂的頭發(fā)上伊履,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音款违,去河邊找鬼唐瀑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛插爹,可吹牛的內(nèi)容都是我干的哄辣。 我是一名探鬼主播请梢,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼力穗!你這毒婦竟也來(lái)了毅弧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤当窗,失蹤者是張志新(化名)和其女友劉穎够坐,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崖面,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡元咙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巫员。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庶香。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖简识,靈堂內(nèi)的尸體忽然破棺而出赶掖,到底是詐尸還是另有隱情,我是刑警寧澤七扰,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布倘零,位于F島的核電站,受9級(jí)特大地震影響戳寸,放射性物質(zhì)發(fā)生泄漏呈驶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一疫鹊、第九天 我趴在偏房一處隱蔽的房頂上張望袖瞻。 院中可真熱鬧,春花似錦拆吆、人聲如沸聋迎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)霉晕。三九已至,卻和暖如春捞奕,著一層夾襖步出監(jiān)牢的瞬間牺堰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工颅围, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伟葫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓院促,卻偏偏與公主長(zhǎng)得像筏养,于是被迫代替她去往敵國(guó)和親斧抱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355