目录javascript
本文是【Rxjs 响应式编程-第四章 构建完整的Web应用程序】这篇文章的学习笔记。html
示例代码托管在:http://www.github.com/dashnowords/blogs前端
博客园地址:《大史住在大前端》原创博文目录java
华为云社区地址:【你要的前端打怪升级指南】git
RxJS-DOMgithub
原文示例中使用这个库进行DOM操做,笔者看了一下github仓库,400多星,并且相关的资料不多,因此建议理解思路便可,至于生产环境的使用仍是三思吧。开发中Rxjs
几乎默认是和Angular
技术栈绑定在一块儿的,笔者最近正在使用ionic3
进行开发,本篇将对基本使用方法进行演示。typescript
冷热Observableexpress
javascript
事件。涉及的运算符编程
bufferWithTime(time:number)
-每隔指定时间将流中的数据以数组形式推送出去。json
pluck(prop:string)
- 操做符,提取对象属性值,是一个柯里化后的函数,只接受一个参数。
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']); }) }
控制台打印的信息能够看到后台的虚拟数据已经被请求到了:
尽管看起来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) ); }
很熟悉吧?通过处理管道后,一次响应中的结果数据被转换为逐个发出的数据,并过滤掉了不符合条件的项:
Angular
中文网列举了最经常使用的一些操做符,RxJS官方文档有很是详细的示例及说明,且均配有形象的大理石图,建议先总体浏览一下有个印象,有须要的读者能够天天熟悉几个,很快就能上手,运算符的使用稍显抽象,且不一样运算符的组合使用在流程控制和数据处理方面的用法灵活多变,也是有不少套路的,开发经验须要慢慢积累。
原文中提到的冷热Observable的差异能够参考这篇文章【RxJS:冷热模式的比较】,概念自己并不难理解。
开发中常会遇到这样一种场景,某些集合型的常量,彻底是能够复用的,一般开发者会将其进行缓存至某个全局单例中,接着在优化阶段,经过增长一个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) }
经过结果能够看出,第二次订阅没有触发网络请求,可是也获得了数据:
网络请求只发送了一次(以前的会发送两次):
这种场景笔者并无进行生产实践,一是由于这种模式须要将数据的变换处理所有经过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);
必定要好好读官方文档。