【响应式编程的思维艺术】 (5)Angular中Rxjs的应用示例

本文是【Rxjs 响应式编程-第四章 构建完整的Web应用程序】这篇文章的学习笔记。

示例代码托管在:http://www.github.com/dashnowords/blogs

博客园地址:《大史住在大前端》原创博文目录

华为云社区地址:【你要的前端打怪升级指南】

图片描述

[TOC]

一. 划重点

  • RxJS-DOM

    原文示例中使用这个库进行DOM操作,笔者看了一下github仓库,400多星,而且相关的资料很少,所以建议理解思路即可,至于生产环境的使用还是三思吧。开发中Rxjs几乎默认是和Angular技术栈绑定在一起的,笔者最近正在使用ionic3进行开发,本篇将对基本使用方法进行演示。

  • 冷热Observable

    • 冷Observable从被订阅时就发出整个值序列
    • 热Observable无论是否被订阅都会发出值,机制类似于javascript事件。
  • 涉及的运算符

    bufferWithTime(time:number)-每隔指定时间将流中的数据以数组形式推送出去。

    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);

五. 一点建议

一定要好好读官方文档。


大史住在大前端
野生码农的鄙视链攀爬指南。

字节跳动前端 | 《前端跨界开发指南》作者

81 声望
27 粉丝
0 条评论
推荐阅读
《前端跨界开发指南》重磅来袭!
2019年,我和机械工业出版社签了协议,约定用1年半时间交付一本15万字的前端技术书籍,起名为《我是前端-JavaScript工程师的自我修养》;

大史不说话阅读 849

安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城31阅读 7.2k评论 5

封面图
涨姿势了,有意思的气泡 Loading 效果
今日,群友提问,如何实现这么一个 Loading 效果:这个确实有点意思,但是这是 CSS 能够完成的?没错,这个效果中的核心气泡效果,其实借助 CSS 中的滤镜,能够比较轻松的实现,就是所需的元素可能多点。参考我们...

chokcoco20阅读 2.1k评论 2

在前端使用 JS 进行分类汇总
最近遇到一些同学在问 JS 中进行数据统计的问题。虽然数据统计一般会在数据库中进行,但是后端遇到需要使用程序来进行统计的情况也非常多。.NET 就为了对内存数据和数据库数据进行统一地数据处理,发明了 LINQ (L...

边城17阅读 1.9k

封面图
【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5.6k评论 10

封面图
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...

边城18阅读 7.7k评论 3

封面图
Vue2 导出excel
2020-07-15更新 excel导出安装 {代码...} src文件夹下新建一个libs文件夹,新建一个excel.js {代码...} vue页面中使用 {代码...} ===========================以下为早期的文章今天在开发的过程中需要做一个Vue的...

原谅我一生不羁放歌搞文艺14阅读 19.9k评论 9

字节跳动前端 | 《前端跨界开发指南》作者

81 声望
27 粉丝
宣传栏