RxJS 操作及其在 Angular 应用中的实践

RxJS 是一个让开发者能够方便地处理异步的 JavaScript 库。通过 RxJS,我们可以使用响应式编程的思想,将所有异步操作都当作流来处理,使得我们的代码更加简洁、易于维护。本文将深入介绍 RxJS 的操作以及在 Angular 应用中的实践,内容详细、有深度、学习和指导意义,读者可以在阅读本文后获得深入理解和使用 RxJS 的能力。

Observable 和 Subscriber

在 RxJS 中,最核心的概念是 Observable 和 Subscriber。Observable 表示一个数据流,可以是一个事件、一个 HTTP 请求的响应,甚至可以是用户输入的一次键盘敲击;而 Subscriber 则表示对这个数据流的订阅。在 RxJS 中,我们可以使用一些操作符(Operators)来对数据流进行操作,例如对流进行过滤、转换、组合等等。

在 Angular 中,我们通常会使用 HttpClient 来进行 HTTP 请求。然而,HttpClient 所返回的值并不是一个简单的值,而是一个 Observable。因此,我们需要对返回的 Observable 进行订阅操作,以获取我们需要的数据。

下面是一个简单的实例,当用户在输入框中输入文字时,会根据这个文字去请求后台数据,并将请求结果展示在页面上:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-search',
  template: `
    <input #searchBox (input)="search(searchBox.value)" />
    <ul>
      <li *ngFor="let result of results">{{ result }}</li>
    </ul>
  `,
})
export class SearchComponent {
  results: string[];
  constructor(private http: HttpClient) {}

  search(term: string): void {
    this.http
      .get<string[]>(`/api/search?term=${term}`)
      .subscribe((results) => (this.results = results));
  }
}

在上面的代码中,我们将用户输入的文字作为参数传递给 search() 函数,这个函数会返回一个 Observable,通过使用 debounceTime() 操作符来延迟请求并防止用户频繁请求。distinctUntilChanged() 操作符会确保只有在查询参数变化时才会发起请求。switchMap() 操作符会处理返回的 Observable,将其转化为我们需要的结果。

操作符

RxJS 提供了大量的操作符,我们可以使用这些操作符对 Observable 进行转换、过滤、组合等等操作。下面就介绍其中一些常用的操作符。

map()

map() 操作符会将 Observable 中的每个值,使用传入的函数进行转换,返回一个新的 Observable。例如:

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

const observable = from([1, 2, 3, 4]);
observable.pipe(map((x) => x * 2)).subscribe((value) => console.log(value));
// 输出:2,4,6,8

可以看到,map() 操作符将原来的 Observable 中的每个数都乘以 2,最后返回一个乘以 2 的新的 Observable。

filter()

filter() 操作符会从一个 Observable 中过滤出符合条件的值,返回一个新的 Observable。例如:

import { from } from 'rxjs';
import { filter } from 'rxjs/operators';

const observable = from([1, 2, 3, 4]);
observable.pipe(filter((x) => x % 2 === 0)).subscribe((value) => console.log(value));
// 输出:2,4

可以看到,filter() 操作符将原来的 Observable 中所有的奇数过滤掉,实现了只输出偶数的目的。

debounceTime()

debounceTime() 操作符会等待一段时间,如果 Observable 在这段时间内没有新的值被发射出来,就会将这个值发射出去。这个操作符常常用来防止用户操作过于频繁,例如搜索框的联想功能可以使用 debounceTime()。例如:

import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

const input = document.querySelector('input');
fromEvent(input, 'input')
  .pipe(debounceTime(1000))
  .subscribe(() => console.log('用户输入完成'));

上面的代码会在用户输入完成后,等待 1 秒才会输出“用户输入完成”。

switchMap()

switchMap() 操作符用来处理 Observable,它可将 Observable 转化为返回另一个 Observable 的函数。例如:

import { from } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const outerObservable = from([1, 2, 3]);
const innerObservable = from([4, 5, 6]);
outerObservable
  .pipe(switchMap(() => innerObservable))
  .subscribe((value) => console.log(value));
// 输出:4, 5, 6, 4, 5, 6, 4, 5, 6

上面的代码中,outerObservable 作为一个外部 Observable,内部订阅了一个 innerObservable。当 outerObservable 中有新的值被发射后,switchMap() 会取消以前订阅的内部 Observable 并订阅新的内部 Observable,最终输出的是 innerObservable 中的所有值。

tap()

tap() 操作符用来查看 Observable 中的值但不影响值的传递,它对于调试代码很有用。例如:

import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

of(1, 2, 3)
  .pipe(tap((value) => console.log(value)))
  .subscribe();
// 输出:1, 2, 3

上面的代码中,tap() 操作符不会进行任何操作,仅仅将 Observable 中的每个值输出到控制台上。

实践

下面是一些在 Angular 应用中实践 RxJS 的例子。

处理 HTTP 请求

在 Angular 中,我们一般使用 HttpClient 对 HTTP 请求进行处理。HttpClient 所返回的是一个 Observable,我们可以使用 map()、catchError() 等操作符对其进行操作。例如:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class ApiService {
  constructor(private http: HttpClient) {}

  getData(): Observable<any> {
    return this.http.get(`/api/data`).pipe(
      map((response) => {
        // 在这里对 response 进行操作
        return response;
      }),
      catchError((error) => {
        // 在这里对 error 进行操作
        return Observable.throw(error);
      })
    );
  }
}

上面的代码中,我们通过 ApiService 发起一个 HTTP 请求,将返回的 Observable 通过 map() 操作符处理过后返回给组件使用。如果请求出错,则会调用 catchError(),在这里我们可以对错误进行处理后返回。

处理 Route

在 Angular 中,我们可以使用 Router 对路由进行处理。Router 所返回的值也是一个 Observable,我们可以使用许多操作符对其进行处理。例如:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  templateUrl: './user-detail.component.html',
})
export class UserDetailComponent implements OnInit {
  userId$: Observable<string>;
  constructor(private route: ActivatedRoute) {}

  ngOnInit() {
    this.userId$ = this.route.paramMap.pipe(map((params) => params.get('id')));
  }
}

上面的代码中,我们在组件初始化时对 ActivatedRoute 进行订阅,获得路由参数,并使用 map() 操作符将参数转化为我们需要的值后输出。由于路由参数是一个 Observable(当用户在同一页面间导航时),因此我们必须使用订阅,而不是直接获取参数。

处理输入框

在 Angular 中,我们可以使用双向绑定将用户输入的值与组件中的属性绑定在一起。RxJS 使得我们可以对用户输入进行进一步的处理,例如使用 debounceTime() 操作符来防止用户频繁地发起请求。例如:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component({
  selector: 'app-search',
  template: `
    <input [(ngModel)]="searchTerm" />
    <ul>
      <li *ngFor="let result of results">{{ result }}</li>
    </ul>
  `,
})
export class SearchComponent {
  results: string[];
  searchTerm = '';
  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    this.search(this.searchTerm);
  }

  search(term: string): void {
    this.http
      .get<string[]>(`/api/search?term=${term}`)
      .subscribe((results) => (this.results = results));
  }

  handleSearch(term: string): void {
    of(term)
      .pipe(
        debounceTime(1000),
        distinctUntilChanged(),
        switchMap(() => this.http.get<string[]>(`/api/search?term=${term}`))
      )
      .subscribe((results) => (this.results = results));
  }
}

上面的代码中,我们首先在组件初始化时初始化 results 和 searchTerm,并通过订阅 search() 函数将搜索框的默认值传递到搜索函数中。当用户输入框发生改变时,会调用 handleSearch() 函数,使用 debounceTime()、distinctUntilChanged() 等操作符对用户输入的值进行处理,并使用 switchMap() 操作符将输入的值转化成一个 Observable,并将这个 Observable 传递给订阅函数。

总结

RxJS 是一个非常强大的库,能够方便处理异步操作。在 Angular 中使用 RxJS,我们可以轻松地处理 HTTP 请求、路由和用户输入等操作。本文详细介绍了 RxJS 的操作符和在 Angular 应用中的实践,希望读者通过本文的学习,能够获得深入理解和使用 RxJS 的能力。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65a26511add4f0e0ffa899c6


纠错反馈