Angular18 RXJS

 

1 RX

  全称是 Reactive Extensions,它是微软开发并维护的基于 Reactive Programming 范式实现的一套工具库集合;RX结合了观察者模式、迭代器模式、函数式编程来管理事件序列

  RX官方文档:点击前往  

 

2 RXJS

  RXJS就是RX在JavaScript层面上的实现;RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 Observable,附属类型 (Observer、 Schedulers、 Subjects) 和受 [Array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。

  RXJS官方文档:点击前往

 

3 RXJS中解决异步事件管理的一些基本概念

  3.1 Observable

    可观察对象:表示一个可调用的未来值或者事件的集合

    官方文档:点击前往

  3.2 Observer

    观察者对象:一个回调函数集合,它知道怎么去监听被可观察对象Observable发送的值

    官方文档:点击前往

  3.3 Subscription

    订阅:表示一个可观察对象Observable的执行,主要用于取消可观察对象Observable执行

    官方文档:点击前往

  3.4 Operators

    操作符:就是一些义函数式编程来处理可观察对象Observable,使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。

    官方文档:点击前往

  3.5 Subject

    相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。

    官方文档:点击前往  

  3.6 Schedulers

    用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeout 或 requestAnimationFrame 或其他。

    官方文档:点击前往

 

4 简单实例

  技巧01:利用 jsbin 这个在线的JS编辑器作为测试编辑器,连接地址 -> 点击前往

  技巧02:本博文使用JS版本都是ES6

  4.1 单击按钮实例

    4.1.1 在HTML中通过script标签引入RXJS库 

<script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>

    4.1.2 在HTML中添加一个button标签   

<button id="btn" >单击查看效果</button>

    4.1.2 通过JS实现RXJS编程   

const btn = document.getElementById('btn');
const btn$ = Rx
             .Observable
             .fromEvent(btn, 'click');
btn$.subscribe(value => console.log('你点击了按钮哟'));

    代码解释:

      第一行:获取ID为“btn”的DOM节点

      第二行:将获取到的DOM节点的单机事件添加到一个可观察对象Observable的序列中

      第三行:订阅可观察对象Observable,当对应DOM节点的单机事件被触发时就会执行相应的操作(本实例是打印出一些信息)

    4.1.3 效果展示

      

    4.1.4 代码汇总

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  
  <button id="btn" >单击查看效果</button>
 
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
</body>
</html>
HTML
const btn = document.getElementById('btn');
const btn$ = Rx
             .Observable
             .fromEvent(btn, 'click');
btn$.subscribe(value => console.log('你点击了按钮哟'));
ES6

  4.2 文本框输入实例

    4.2.1 在HTML中通过script标签引入RXJS库    

<script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>

    4.2.2 在HTML中添加一个input标签 

<input id="test" type="text" placeholder="请输入一些信息" />

    4.2.3 通过JS实现RXJS编程  

const test = document
               .getElementById('test');
const test$ = Rx
                .Observable
                .fromEvent(test, 'keyup')
                .debounceTime(500)
                .pluck('target', 'value');
test$
  .subscribe(value => console.log(value));

      代码解释:

        fromEvent -> 将DOM节点的keyup事件添加到可观察对象Observable中

        debounceTime -> DOM节点的keyup事件触发后0.5秒后才推送

        pluck -> 获取触发事件DOM节点的value值

    4.2.4 效果展示

      

    4.2.5 代码汇总     

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  
  <input id="test" type="text" placeholder="请输入一些信息" />
  
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
</body>
</html>
HTML
const test = document
               .getElementById('test');
const test$ = Rx
                .Observable
                .fromEvent(test, 'keyup')
                .debounceTime(500)
                .pluck('target', 'value');
test$
  .subscribe(value => console.log(value));
ES6

 

 5 弹珠图

  要解释操作符是如何工作的,文字描述通常是不足以描述清楚的。许多操作符都是跟时间相关的,它们可能会以不同的方式延迟(delay)、取样(sample)、节流(throttle)或去抖动值(debonce)。图表通常是更适合的工具。弹珠图是操作符运行方式的视觉表示,其中包含输入 Obserable(s) (输入可能是多个 Observable )、操作符及其参数和输出 Observable 。

  

 

6 可观察对象Observable

  技巧01:可观察对象Observable是数据的产生者,它会将数据以流的方式推送给观察者Observer;可以将可观察对象Observable看作是一个可以同步、异步返回多个值的函数

const test$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    observer.next(2);    
                    observer.next(3);
                    setTimeout(() => observer.next('一秒钟后异步推送的数据'), 1000);
                    setTimeout(() => {
                      observer.next('2秒钟后异步推送的数据');
                      observer.complete();
                    }, 2000);
                  });

    代码解释:上面的代码中利用create操作符创建了一个Observable,该Observable一旦被订阅就会向Observer推送1、2、3,然后1秒钟后推送“一秒钟后异步推送的数据”,再过1秒钟后推送“2秒钟后异步推送的数据”,紧接着是完成数据流的推送

  技巧02:订阅Observable是Observable执行和发送值/事件给Observer最简单的方式;订阅Observable就类似于调用一个拥有多个返回值的函数

const test$Sub = test$.subscribe(
                          value => console.log(value),
                          error => console.log(error),
                          () => console.log('数据推送完成')                        
                 );

    代码解释:上面的代码是订阅Observable的一个实例test$,订阅方法subscribe中的参数是有顺序的,其中第一个参数是位置参数用于处理数据,第二三个参数是可选参数,其中第二个参数用来处理错误,第三个参数用来处理数据推送完成后的一些操作;订阅后会返回一个Subscription对象,可以通过该对象来关闭正在执行的Observable

  技巧03:利用Subscription对象来关闭正在执行的Observable

const test$ = Rx.Observable
                  .create(observer => {
//                    const t =  setInterval(() => observer.next('hello'), 1000);
                    observer.next(1);
                    observer.next(2);    
                    observer.next(3);
                    const st01 = setTimeout(() => observer.next('一秒钟后异步推送的数据'), 1000);
                    const st02 = setTimeout(() => {
                      observer.next('2秒钟后异步推送的数据');
                      observer.complete();
                    }, 2000);
                    
                    return function unsubscribe() {
                      clearTimeout(st01);
                      clearTimeout(st02);
//                       clearInterval(t);
                    }
                  });
const test$Sub = test$.subscribe(
                          value => console.log(value),
                          error => console.log(error),
                          () => console.log('数据推送完成')                        
                 );
setInterval(() => test$Sub.unsubscribe(), 1000);
// test$Sub.unsubscribe();

    代码解释:使用create创建Observable的时候回返回一个函数unsubscribe,该函数用来定义如何清理执行的资源;订阅该Observable后就会返回一个Subscription对象,通过该Subscription对象来调用unsubscribe方法来取消正在执行的Observable

 

7 观察者Observer

  Observer是Observable推送的值的消费者;Observer是一组回调函数的集合,每个回调函数对应一种 Observable 发送的通知类型:nexterror 和 complete。下面的示例是一个典型的观察者对象:

var observer = {
  next: x => console.log('Observer got a next value: ' + x),
  error: err => console.error('Observer got an error: ' + err),
  complete: () => console.log('Observer got a complete notification'),
};

  技巧01:使用Observer就必须将他们提供给Observable的subscirbe方法

  坑01:虽然所Observer是一个对象,但是在将Observer传给Observable的subscribe方法时只需要将对象中的内容传进去就可以啦,例如下面第一种方式就是错误的,第二种方式才是正确的

test$.subscribe(
                          {
                               value => console.log(value),
                               error => console.log(error),
                               () => console.log('数据推送完成')   
                          }                     
                 );  
错误的使用方法
test$.subscribe(
                          value => console.log(value),
                          error => console.log(error),
                          () => console.log('数据推送完成')                        
                 );

 

8 操作符

  操作符是 Observable 类型上的方法,比如 .map(...).filter(...).merge(...),等等。当操作符被调用时,它们不会改变已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable。(操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变)

  操作符本质上是一个纯函数 (pure function),它接收一个 Observable 作为输入,并生成一个新的 Observable 作为输出。订阅输出 Observalbe 同样会订阅输入 Observable 。

  8.1 小案例之mapTo

    每次源 Observble 发出值时,都在输出 Observable 上发出给定的常量值;接收常量 value 作为参数,并每当源 Observable 发出值时都发出这个值。换句话说, 就是忽略实际的源值,然后简单地使用这个发送时间点以知道何时发出给定的 value

    

    8.1.1 效果展示

      

    8.1.2 代码汇总  

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <button id="btn" type="button">点击测试</button>
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
</body>
</html>
HTML
// 根据ID获取DOM对象
const btn = document.getElementById("btn");

// 将对应DOM对象的单击事件变成事件流
const btn$ = Rx.Observable
                .fromEvent(btn, "click")
                .mapTo('ello');

// 订阅事件流
btn$.subscribe(value => console.log(value));
ES6

 

  8.2 创建类操作符

    8.2.1 interval

public static interval(period: number, scheduler: Scheduler): Observable

      创建一个 Observable ,该 Observable 使用指定的 IScheduler ,并以指定时间间隔发出连续的数字。

      

      坑01:interval产生的第一个数并不是立即就产生的,而是当第一个时间间隔过后才会推送第一个数字,而且推送的数字是从0开始的自增数字

const test$ = Rx.Observable
                .interval(1000);
test$.subscribe(value => console.log(value));

      技巧01:官方文档 => 点击前往

    8.2.2 timer

public static timer(initialDelay: number | Date, period: number, scheduler: Scheduler): Observable

      根据给定的时间间隔自增的产生数字

      参数解释:

        参数01:产生第一个数字的间隔时间

        参数02:产生下一个数字的时间间隔(可选)

        参数03:调度器,用来调度值的发送, 提供“时间”的概念  (可选,默认值: async)

      坑01:如果只给定第一个参数,那么会在时间间隔到达后推送数字0,然后就不在继续推送数据啦

      技巧01:其实timer的用法和interval非常想,都是自动产生自动的数字

      

      

const test$ = Rx.Observable
                .timer(1000, 1000)
                .take(3)
                .map(value => value * 10);
test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

 

 

  8.3 合并类操作符

    8.3.1 combineLatest

      组合多个 Observables 来创建一个 Observable ,该 Observable 的值根据每个输入 Observable 的最新值计算得出的;组合多个 Observables 来创建一个 Observable ,该 Observable 的值根据每个输入 Observable 的最新值计算得出的。

      

      技巧01:每个Observables都必须有值,而且他们其中必须有一个的值更新后才会执行combineLatest

      》效果展示

        

      》代码汇总

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <input type="number" id="weight" placeholder="请输入体重" />
  <span>Kg</span>
  <br />
  <input type="number" id="height" placeholder="请输入身高" />
  <span>cm</span>
  
  <script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js"></script>
</body>
</html>
HTML
const weight = document.getElementById('weight');
const height = document.getElementById('height');

const weight$ = Rx.Observable
                  .fromEvent(weight, 'keyup')
                  .debounceTime(500)
                  .pluck('target', 'value');
const height$ = Rx.Observable
                  .fromEvent(height, 'keyup')
                  .debounceTime(500)
                  .pluck('target', 'value');

const bmi$ = Rx.Observable
               .combineLatest(
                 weight$, 
                 height$, 
                 (w, h) => {
                   const str = '成人BMI数值为:';
                   const bmi = w / (h * h / 100 / 100);
                   if (bmi < 18.5) {
                     return str + bmi + ' -> 过轻';
                   } else {
                     if (bmi < 23.9) {
                       return str + bmi + ' -> 正常';
                     } else {
                       if (bmi < 27) {
                         return str + bmi + ' -> 过重';
                       } else {
                         if (bmi < 32) {
                           return str + bmi + ' -> 肥胖';
                         } else {
                           return str + bmi + ' -> 非常肥胖';
                         }
                       }
                     }
                   }
                  
                 }
               );

// 过轻:低于18.5
// 正常:18.5-23.9
// 过重:24-27
// 肥胖:28-32
// 非常肥胖, 高于32
// weight$.subscribe(value => console.log(value));
// height$.subscribe(value => console.log(value));
bmi$.subscribe(value => console.log(value));
ES6

    8.3.2 merge

public merge(other: ObservableInput, concurrent: number, scheduler: Scheduler): Observable

      将两个数据源的数据合并到一起

      

      

const test01$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    setTimeout(() => {
                      observer.next(2);
                    }, 1000);
                    observer.next(3);
                  });
const test02$ = Rx.Observable
                  .create(observer => {
                    observer.next(4);
                    observer.next(5);
                    observer.next(6);
                  });
const test$ = Rx.Observable
                .merge(test01$, test02$);

test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
View Code

    8.3.3 startWith

public startWith(values: ...T, scheduler: Scheduler): Observable

      向原数据源添加一个数据作为第一个推送的数据

      

      

const test01$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    setTimeout(() => {
                      observer.next(2);
                    }, 1000);
                    observer.next(3);
                  })
                  .startWith('HELLO');

test01$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
View Code

    8.3.4 zip

      根据顺序将两个数据源中的数据进行组合

       技巧01:如果一个数据源中有3个数据,另外一个数据源中有4个数据,那么只会将这两个数据源中前面推送的3个数据进行组合

      

const test01$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    setTimeout(() => {
                      observer.next(2);
                    }, 1000);
                    observer.next(3);
                  });
const test02$ = Rx.Observable
                  .create(observer => {
                    observer.next('a');
                    observer.next('b');
                    observer.next('c');
                    observer.next('d');
                  });

const test$ = Rx.Observable
                .zip(test01$, test02$);

test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

  

  8.4 其他操作符

    8.4.1 take

public take(count: number): Observable<T>

      只会推送原数据流中前面的count个数据      

      

const test$ = Rx.Observable
                .interval(1000)
                .take(3);
test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );

      

    8.4.2 map

public map(project: function(value: T, index: number): R, thisArg: any): Observable<R>

      对原数据源的每个值做同样的处理

       

       

const test$ = Rx.Observable
                .interval(1000)
                .take(3)
                .map(value => value * 10);
test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

     8.4.3 filter 

public filter(predicate: function(value: T, index: number): boolean, thisArg: any): Observable

      对原数据源的数据进行过滤

      参数解释:

        参数01:过滤操作函数,操作函数中第一个参数代表正在过滤的数据,第二个参数代表原数据源序列的索引(注意:索引是从0开始的,用处还问弄清楚)???

        参数02:可选参数,用来决定 predicate 函数中的 this 的值

      

       

      

const test$ = Rx.Observable
                .timer(1000, 1000)
                .take(5)
                .map(value => value * 10)
                .filter((value) => value > 10);
test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

    8.4.4 first

public first(predicate: function(value: T, index: number, source: Observable<T>): boolean, resultSelector: function(value: T, index: number): R, defaultValue: R): Observable<T | R>

      只推送原数据源中的第一个值或者第一个满足条件的值

      技巧01:如果first不携带任何参数,那么将会推送原数据源中的第一个值(和take(1)的效果相同);如果携带一个返回boolean的方法,那么将返回满足该方法的第一个数据

      

      

const test$ = Rx.Observable
                .timer(1000, 1000)
                .take(5)
                .map(value => value * 10)
//                 .first();
                .first((value) => value > 10);
test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

     8.4.5 last 

public last(predicate: function): Observable

      只推送原数据源中的最后一个数据

      技巧01:如果last不指定参数时,就会默认只推送最后一个数据;如果指定了一个返回boolean类型的方法作为参数,那么就会返回最后一个满足条件的数据

       

      

const test$ = Rx.Observable
                .interval(1000)
                .take(5)
                .last();
//                 .last(value => value < 3 );

test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

    8.4.6 skip  

public skip(count: Number): Observable

      在推送数据时,跳过前面count个数据

      

      

const test$ = Rx.Observable
                .interval(1000)
                .take(5)
                .skip(2);

test$.subscribe(
        value => console.log(value),
        error => console.log(error),
        () => console.log('完成')
      );
ES6

    8.4.7 scan

public scan(accumulator: function(acc: R, value: T, index: number): R, seed: T | R): Observable<R>

      对原数据源序列进行依次累加,每累加玩完一次就会推送一个结果

      

      参数解释:

        参数01:一个累加方法,该方法的第一个参数代表累加结果,第二个参数代表当前累加值,该累加值随便用一个变量表示即可

        参数02:可选参数,累加器的初始值,如果不填就会默认将原数据源的第一个数据作为初始累加值

      

const b = 100;
const test01$ = Rx.Observable
                  .interval(1000)
                  .take(5)
                  .scan((acc, a) => acc + a, b);

test01$.subscribe(
          value => console.log(value),
          error => console.log(error),
          () => console.log('完成')
        );
ES6

     8.4.8 reduce

public reduce(accumulator: function(acc: R, value: T, index: number): R, seed: R): Observable<R>

      对原数据源中的数据进行累加,最后只将累加的结果推送

      参数解释:

        参数01:第一个参数是一个累加方法,该累加方法接受两个参数,第一个参数代表累加结果,第二个参数代表当前累加值

        参数02:可选参数,该参数如果指定了就会被用作累加的初始值

      

      

const b = 100;
const test01$ = Rx.Observable
                  .interval(1000)
                  .take(5)
                  .reduce((acc, a) => acc + a, b);

test01$.subscribe(
          value => console.log(value),
          error => console.log(error),
          () => console.log('完成')
        );
ES6

    8.4.9 debounceTime

public debounceTime(dueTime: number, scheduler: Scheduler): Observable

      当没有新的数据添加到原数据源时,延迟发送原数据源中的数据

      

      

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>

  
  <input type='text' id="test" placeholder="测试文本框" />

  <script src="https://unpkg.com/@reactivex/rxjs@5.0.3/dist/global/Rx.js">
  </script>
</body>
</html>
HTML
const test = document.getElementById('test');
const test$ = Rx.Observable
                .fromEvent(test, 'keyup')
                .debounceTime(500)
                .pluck('target', 'value');

test$.subscribe(
       value => console.log(value),
       error => console.log(error),
       () => console.log('完成')
     );
ES6

     8.4.10 distinct

public distinct(keySelector: function, flushes: Observable): Observable

      去除掉原数据源中重复的数据

      技巧01:如果distinct方法提供了keySelector方法,就会先利用keySelector方法对原数据源你的数据进行处理后在进行去重操作

      官方文档 => 点击前往

        

const test01$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    observer.next(2);
                    observer.next(3);
                    observer.next(1);
                    observer.next(2);
                    observer.next(3);
                  })
                  .distinct();
test01$.subscribe(value => console.log(value));
ES6

    8.4.11 distinctUntilChanged

public distinctUntilChanged(compare: function): Observable

      去除掉原数据源相邻数据的重复数据

       

const test01$ = Rx.Observable
                  .create(observer => {
                    observer.next(1);
                    observer.next(2);
                    observer.next(3);
                    observer.next(3);
                    observer.next(3);
                    observer.next(1);
                    observer.next(2);
                    observer.next(3);
                  })
                  .distinctUntilChanged();
test01$.subscribe(value => console.log(value));
ES6

 

posted @ 2018-01-23 22:57  寻渝记  阅读(500)  评论(0编辑  收藏  举报