【響應式編程的思惟藝術】 (5)Angular中Rxjs的應用示例

本文是【Rxjs 響應式編程-第四章 構建完整的Web應用程序】這篇文章的學習筆記。html

示例代碼託管在:http://www.github.com/dashnowords/blogs前端

博客園地址:《大史住在大前端》原創博文目錄java

華爲雲社區地址:【你要的前端打怪升級指南】git

一. 劃重點

  • RxJS-DOMgithub

    原文示例中使用這個庫進行DOM操做,筆者看了一下github倉庫,400多星,並且相關的資料不多,因此建議理解思路便可,至於生產環境的使用仍是三思吧。開發中Rxjs幾乎默認是和Angular技術棧綁定在一塊兒的,筆者最近正在使用ionic3進行開發,本篇將對基本使用方法進行演示。typescript

  • 冷熱Observableexpress

    • 冷Observable從被訂閱時就發出整個值序列
    • 熱Observable不管是否被訂閱都會發出值,機制相似於javascript事件。
  • 涉及的運算符編程

    bufferWithTime(time:number)-每隔指定時間將流中的數據以數組形式推送出去。json

    pluck(prop:string)- 操做符,提取對象屬性值,是一個柯里化後的函數,只接受一個參數。

二. Angular應用中的Http請求

Angular應用中基本HTTP請求的方式:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { MessageService } from './message.service';//某個自定義的服務
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class HeroService {
  private localhost = 'http://localhost:3001';
  private all_hero_api = this.localhost + '/hero/all';//查詢全部英雄
  private query_hero_api = this.localhost + '/hero/query';//查詢指定英雄

  constructor(private http:HttpClient) {
  }
  
  /*通常get請求*/
  getHeroes(): Observable<HttpResponse<Hero[]>>{
    return this.http.get<Hero[]>(this.all_hero_api,{observe:'response'});
  }

  /*帶參數的get請求*/
  getHero(id: number): Observable<HttpResponse<Hero>>{
    let params = new HttpParams();
        params.set('id', id+'');
        return this.http.get<Hero>(this.query_hero_api,{params:params,observe:'response'});
  }
  
  /*帶請求體的post請求,any能夠自定義響應體格式*/
  createHero(newhero: object): Observable<HttpResponse<any>>{
      return this.http.post<HttpResponse<any>>(this.create_hero_api,{data:newhero},{observe:'response'});
  } 
}

express中寫一些用於測試的虛擬數據:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/all', function(req, res, next) {
  let heroes = [{
    index:1,
    name:'Thor',
    hero:'God of Thunder'
  },{
    index:2,
    name:'Tony',
    hero:'Iron Man'
  },{
    index:3,
    name:'Natasha',
    hero:'Black Widow'
  }]
  res.send({
     data:heroes,
     result:true
  })
});

/* GET home page. */
router.get('/query', function(req, res, next) {
  console.log(req.query);
  let hero= {
    index:4,
    name:'Steve',
    hero:'Captain America'
  }
  res.send({
     data:hero,
     result:true
  })
});


/* GET home page. */
router.post('/create', function(req, res, next) {
  console.log(req.body);
  let newhero = {
     index:5,
     name:req.body.name,
     hero:'New Hero'
  }
  res.send({
     data:newhero,
     result:true
  })
});

module.exports = router;

在組件中調用上面定義的方法:

sendGet(){
 this.heroService.getHeroes().subscribe(resp=>{
   console.log('響應信息:',resp);
   console.log('響應體:',resp.body['data']);
 })
}

sendQuery(){
this.heroService.getHero(1).subscribe(resp=>{
  console.log('響應信息:',resp);
  console.log('響應體:',resp.body['data']);
})
}

sendPost(){
this.heroService.createHero({name:'Dash'}).subscribe(resp=>{
  console.log('響應信息:',resp);
  console.log('響應體:',resp.body['data']);
})
}

控制檯打印的信息能夠看到後臺的虛擬數據已經被請求到了:

三. 使用Rxjs構建Http請求結果的處理管道

3.1 基本示例

儘管看起來Http請求的返回結果是一個可觀測對象,可是它卻沒有map方法,當須要對http請求返回的可觀測對象進行操做時,可使用pipe操做符來實現:

import { Observable, of, from} from 'rxjs';
import { map , tap, filter, flatMap }from 'rxjs/operators';

/*構建一個模擬的結果處理管道
*map操做來獲取數據
*tap實現日誌
*flatMap實現結果自動遍歷
*filter實現結果過濾
*/
getHeroes$(): Observable<HttpResponse<Hero[]>>{
    return this.http.get<Hero[]>(this.all_hero_api,{observe:'response'})
    .pipe(
          map(resp=>resp.body['data']),
          tap(this.log),
          flatMap((data)=>{return from(data)}),
          filter((data)=>data['index'] > 1)
    );
}

很熟悉吧?通過處理管道後,一次響應中的結果數據被轉換爲逐個發出的數據,並過濾掉了不符合條件的項:

3.2 常見的操做符

Angular中文網列舉了最經常使用的一些操做符,RxJS官方文檔有很是詳細的示例及說明,且均配有形象的大理石圖,建議先總體瀏覽一下有個印象,有須要的讀者能夠天天熟悉幾個,很快就能上手,運算符的使用稍顯抽象,且不一樣運算符的組合使用在流程控制和數據處理方面的用法靈活多變,也是有不少套路的,開發經驗須要慢慢積累。

四. 冷熱Observable的兩種典型場景

原文中提到的冷熱Observable的差異能夠參考這篇文章【RxJS:冷熱模式的比較】,概念自己並不難理解。

4.1 shareReplay與請求緩存

開發中常會遇到這樣一種場景,某些集合型的常量,徹底是能夠複用的,一般開發者會將其進行緩存至某個全局單例中,接着在優化階段,經過增長一個if判斷在請求以前先檢查緩存再決定是否須要請求,Rxjs提供了一種更優雅的實現。

先回顧一下上面的http請求代碼:

getHeroes(): Observable<HttpResponse<Hero[]>>{
   return this.http.get<Hero[]>(this.all_hero_api,{observe:'response'});
}

http請求默認返回一個冷Observable,每當返回的流被訂閱時就會觸發一個新的http請求,Rxjs中經過shareReplay( )操做符將一個可觀測對象轉換爲熱Observable(注意:shareReplay( )不是惟一一種能夠加熱Observable的方法),這樣在第一次被訂閱時,網絡請求被髮出並進行了緩存,以後再有其餘訂閱者加入時,就會獲得以前緩存的數據,運算符的名稱已經很清晰了,【share-共享】,【replay-重播】,是否是形象又好記。對上面的流進行一下轉換:

getHeroes$(): Observable<HttpResponse<Hero[]>>{
    return this.http.get<Hero[]>(this.all_hero_api,{observe:'response'})
    .pipe(
      map(resp=>resp.body['data']),
      tap(this.log),
      flatMap((data)=>{return from(data)}),
      filter((data)=>data['index'] > 1),
      shareReplay() // 轉換管道的最後將這個流轉換爲一個熱Observable
    )
  }

在調用的地方編寫調用代碼:

sendGet(){
     let obs = this.heroService.getHeroes$();
     //第一次被訂閱
     obs.subscribe(resp=>{
       console.log('響應信息:',resp);
     });
    //第二次被訂閱
     setTimeout(()=>{
       obs.subscribe((resp)=>{
         console.log('延遲後的響應信息',resp);
       })
     },2000)
}

經過結果能夠看出,第二次訂閱沒有觸發網絡請求,可是也獲得了數據:

網絡請求只發送了一次(以前的會發送兩次):

4.2 share與異步管道

這種場景筆者並無進行生產實踐,一是由於這種模式須要將數據的變換處理所有經過pipe( )管道來進行,筆者本身的函數式編程功底可能還不足以應付,二來總以爲不少示例的使用場景很牽強,因此僅做基本功能介紹,後續有實戰心得後再修訂補充。Angular中提供了一種叫作異步管道的模板語法,能夠直接在*ngFor的微語法中使用可觀測對象:

<ul>
  <li *ngFor="let contact of contacts | async">{{contact.name}}</li>
</ul>
<ul>
  <li *ngFor="let contact of contacts2 | async">{{contact.name}}</li>
</ul>

示例:

this.contacts = http.get('contacts.json')
                    .map(response => response.json().items)
                    .share();
setTimeout(() => this.contacts2 = this.contacts, 500);

五. 一點建議

必定要好好讀官方文檔。

相關文章
相關標籤/搜索