美文网首页rxjs6RxJS
从Angular6开始入门RxJS6

从Angular6开始入门RxJS6

作者: Ellite | 来源:发表于2019-05-08 00:39 被阅读0次

    简介

    本文的读者受众

    • 正准备学习Angular的人
    • 想要知道Rx和RxJS相关知识的人

    这篇文章是什么?

    Angular使用RxJS标准库来有效地实现异步处理。
    为了使用好 RxJS,需要考虑到与传统编程的不同之处。
    就我而言,在什么都不知道的状态下阅读官方文档,我也不明白它的优势或具体用法。
    不能理解的最大因素是我并没有形成RX的概念印象。
    如果我从一开始就拥有这个概念印象,我认为我的学习会更顺利...... orz

    所以在这篇文章中

    • 我想现在开始使用Angular 6,但我还需要学习一个名为RxJS的库...
    • 我想学习Rx,但我不知道从哪里开始
    • 我想知道Rx是什么

    将以这些方向,对RxJS使用的优点和他的概念、经常使用的方法进行解释。

    RxJS

    反应式扩展
    Reactive Extensions(Rx)是,一个使用可观察数据序列和类LINQ样式的请求运算符,来创建(实现)异步及基于事件程序的技术库。

    数据序列,拥有各种形式的存在。例如来自文件和Web服务的数据流,对Web服务的请求,系统通知以及用户操作的事件。

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

    • 在简单阅读完上面链接内容后,继续以下内容

    在Angular中使用RxJS的优点

    Rx的世界中,处理的值并非固定,而是可以不断变化的数据流。
    您可以在流中放置任何内容,例如用户操作的事件值活API响应结果等异步值,或数字和字符串等同步值。

    任何值都可以在数据流中流入,Rx提供的通用格式进行数据的加工和时机的处理

    无论是事件还是API响应,您都可以使用相同的格式并通过便捷的代码段编写外观漂亮的代码。
    这是使用Rx的最大好处。
    因为JavaScript缺少用于操作数组和对象的标准API,所以我认为有很多机会使用名为lodash的库。
    我认为Rx是Promise版的 lodash


    我称之为“时序处理”的是具体的以下实现。

    • 控制高速连续的事件在每50ms发生
      • 控制经常出现浏览器滚动控件
    • 最后一次检测到事件后100毫秒
      • 频繁出现的表单相关处理
    • 事件在一定时间内发生过多次
      • 控制双击等

    在Angular ( SPA )中,时序相关的处理是很频繁的,如果每次都使用标准的 setTimeout 等方法实现的话,会有很多性能的浪费,而且代码也会变得极其难以阅读、理解。
    通过使用RxJS,您可以将所有数据的合并、过滤、映射与时间轴的处理,轻松地放在一起实现。

    Rx的印象

    理解事物最重要的是印象的转换。
    在本章中,我们将之前所罗列的Rx概念升华为印象。

    这次我为那些根本不了解Rx的人做了一个简单的故事。
    通过跟踪这个故事,我认为您可以学习到Rx的粗略概念。


    • 在夏季时,有一条流淌桃子的河流( stream )
      • 当然,除了桃子 ( value ) 以外也有魚 ( value )在流动。
      • 在秋、冬、春时不会流淌桃子
    • 您想利用这条神秘的河流,制造出材料成本为0的桃子罐头,然后贩卖赚大钱。
    • 因此,我们需要建立一个系统,可以自动从河流中收集 ( filter )桃子、将其转换成桃子罐头 ( map )
      • 系统的运行需要消耗电力
    • 夏天的时候运行系统 ( subscribe )
    • 夏天以外的时候不会有桃子,所以要关闭系统( unsubscribe ),以便他不消耗不必要的电力

    Rx最重要的概念为“流”,所以经常用河流做比较。
    现实中的河流,在没有我们做任何事情的情况下也会继续自由地流动。
    像上面的故事中的一样,管理者打开装载的开关,监视 ( subscribe ) 河流、在罐头制造装置 ( operators ) 接收桃子( value ) 时,执行期望的处理。

    Rx初学者刚开始经常遇到的问题是,因为忘记使用subscribe,而一直在查找值不流出的原因。
    但是,如果您从一开始就能想到故事,就不会把时间浪费在这种类似的错误上。

    此外,故事中出现的电力,就现实而言也就是客户端的CPU资源。与每个月发生的电费不同,内存泄漏经常出现在债务堆积的情况下。
    因此,当您销毁组件时,请务必记住取消订阅内部订阅的流。

    最后,将此故事转换为实际代码如下所示:

    private subscription: Subscription;
    
    ngOnInit() {
        this.subscription = of('桃子', '鲤鱼').pipe(
            filter(v => v === '桃子'), 
            map(v => v + '罐头')
        ).subscribe(console.log);
    }
    
    ngOnDestroy() {
        this.subscription.unsubscribe();
    }
    
    桃子罐头
    

    RxJS的概念总结

    我认为可以在上一章的故事中大致理解Rx的概念,但我将再次回到原文。

    在官方指南中,Rx库由以下公式表示:

    Rx = Observables + LINQ (Operators) + Schedulers
    

    正如我前面提到的,Observables是河流,事件,异步处理等的可观察对象,也是流的起点。
    此外,Operators 可以被视为决定如何处理流中流动值的设备。
    一旦订阅后,您可以使用反应式编程执行一系列流处理。

    Subject

    Subject类经常以各种方式使用,例如在Rx的逻辑中通知或临时存储值。

    Subject结合上面的例子来说,类似于大坝。
    大坝连接到河流,可以观察从大坝流出的价值,另外,也可以从外部设定值。
    他就像流版的变量一样。

    Subject有很多种类型,所以不能一概而论,但我认为它的概念印象是下面的图像。

    重复出现的 Observable 和 Operators

    如果上面说的已经全部理解的话,之后再有什么样的川 ( Observable ) 、什么样的装置( Operators ) 、剩下的工作仅仅是记住它们罢了。

    这一次,我试图整理一个简单的使用场景,专注于我经常使用的东西。
    此列表优先考虑具体的用途和印象,因为如果每个文本中都包含详细说明,则文字数量将是巨大的。
    有关详细用法,请参阅官方文档。

    Observable

    from

    • 将Promise 或者 iterator 的值 ( string、array 等 ) 转换为 Observable

    使用例:处理API响应结果并检索所需的值

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

    fromEvent

    • 将event转换为 Observable

    使用例:双击的捕捉

    const click$ = fromEvent(document, 'click');
    
    click$.subscribe(console.log);
    

    merge

    • 合并流动的值

    使用场景:将各种事件合成为一个触发器

    const click$ = fromEvent(targetElement, 'click');
    const mouseover$ = fromEvent(targetElement, 'mouseover');
    
    merge(click$, mouseover$).subscribe(() => {
      // 期望的处理
    });
    

    of

    • 将值转换为 Observable

    使用场景:测试、确认用、流分裂或结合时的搭配

    const hoge$ = of(1, 2, 3);
    const huga$ = fromEvent(document, 'click');
    
    merge(hoge$, huga$).subscribe(console.log);
    

    interval

    • 定时流动的值

    使用场景:显示已用时间

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

    concat

    • 保存流的顺序并结合

    使用场景:在应用中保存缓存内容和API响应的合成,并立即显示缓存 → 切换到准确的数据

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

    Operators

    tap ( 旧 do )

    • 在不影响流的情况下进行任何处理

    使用场景:日志显示

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

    map / pluck

    • 流的値的加工・转换・抽出

    使用场景:处理API响应结果(抽出需要的值)

    const apiResponse$ = of({ userId: 1, body: 'hoge huga piyo' });
    
    const userId$ = apiResponse$.pipe(map(v => v.userId));
    // ---------------------------------------------------
    // 如果只想要取得值,可以使用pluck,让代码更简洁
    const userId$ = apiResponse$.pipe(pluck('userId'));
    

    filter

    • 过滤值

    使用场景:重定向事件时显示加载进度条

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

    skip

    • 跳过值

    使用场景:跳过组件生成后联动处理

    // 跳过第一次流动的值,因为它不是用户操作更改的值
    this.route.params.pipe(pluck('categoryId'), skip(1)).subscribe(categoryId => {
        console.log(`changed categoryId: ${ categoryId }`);
    });
    

    scan

    • 使用以前的值

    使用场景:无限滚动条的项目列表管理

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

    take

    • 确定值流动的次数

    使用场景:只使用变动值的最初 x 回

    // 如果不使用take(1) 的话,每回store值更新的时候,都会调用API
    this.store.select(getUserId).pipe(
        take(1), 
        concatMap(userId => this.apiService.get(userId))
    ).subscribe();
    

    startWith

    • 指定最初流动的值

    使用场景:显示经过的时间(改良版)

    ※ 如果仅使用 interval ,则第一秒将不会显示任何内容。

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

    takeUntil

    • 值流动时的暂停处理

    使用场景:在销毁组件时通过Subject发送结束流的通知

    ※ 但请注意、这篇文章 介绍的内存泄露

    private onDestroy$ = new Subject();
    
    ngOnInit() {
        interval(1000).pipe(takeUntil(this.onDestroy$)).subscribe(console.log);
    }
    
    ngOnDestroy() {
        this.onDestroy$.next();
    }
    

    concatMap

    • 将值转变成Observable 后合并(执行中的处理结束后转到下一个操作处理)

    使用场景:使用API1响应结果调用API2

    switchMap

    • 将值转变成Observable 后合并(下一个値过来时,中断正在执行的处理)

    使用场景:实现 auto complete 功能

    debounceTime

    • 弃掉在两次输出之间小于指定时间的发出值

    使用场景:实现 auto complete 功能

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

    throttleTime

    • 控制值流动的速度

    使用场景:控制滚动条事件

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

    withLatestFrom

    • 与合并后流最新的值进行合并

    使用场景:将点击的用户ID作为GA事件发送

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

    combineLatest

    • 如果对主流和合成流都进行了更改,则会发送每个流的最新值

    使用场景:表单输入值和store信息的合并

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

    publish, share, refCount...etc

    • cold流转化为hot流

    cold / hot 的概念在这篇文章 中介绍

    使用场景:使用API1响应结果并行执行API2和API3

    ※ 将 API1 流 hot 化后,可防止多次调用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);
    

    关于RxJS6的导入

    Observable、Subject 以及 Subscription

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

    Operators

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

    结束

    image

    翻译自

    相关文章

      网友评论

        本文标题:从Angular6开始入门RxJS6

        本文链接:https://www.haomeiwen.com/subject/mmcjoqtx.html