为什么需要 RxJS?
作为前端开发者,我们每天都在处理异步事件:用户点击、HTTP 请求、定时器、WebSocket 消息……传统做法是使用回调或 Promise,但当多个事件源需要组合、过滤、转换时,代码会迅速变得难以维护。
RxJS 将一切视为可观察的流(Observable),提供了强大的操作符来声明式地处理异步数据。它不仅能简化复杂逻辑,还能轻松实现防抖、节流、重试、竞态取消等高级功能。
本文将通过一个实时搜索的完整示例,带你从零上手 RxJS。
环境准备
首先,创建一个新项目并安装 RxJS:
mkdir rxjs-demo && cd rxjs-demo
npm init -y
npm install rxjs
为了运行示例,我们使用 Node.js(需要支持 ES Modules)。在 package.json 中添加 "type": "module"。
核心概念:Observable、Observer 和 Subscription
Observable 代表一个可调用的未来值或事件的集合。Observer 是一个回调集合,知道如何监听 Observable 发出的值。Subscription 表示 Observable 的执行,主要用于取消执行。
让我们创建一个简单的 Observable:
import { Observable } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next('Hello');
subscriber.next('RxJS');
setTimeout(() => {
subscriber.next('World');
subscriber.complete();
}, 1000);
});
const subscription = observable.subscribe({
next: value => console.log(value),
error: err => console.error(err),
complete: () => console.log('完成')
});
// 取消订阅
// subscription.unsubscribe();
输出顺序:Hello、RxJS,1 秒后 World 和 完成。
> 注意:Observable 是惰性的,只有被订阅时才会执行。每次订阅都会重新执行生产者函数。
创建操作符:从事件到流
RxJS 提供了许多创建操作符,如 of、from、fromEvent、interval 等。
import { of, from, fromEvent, interval } from 'rxjs';
import { take } from 'rxjs/operators';
// 从数组创建
from([1, 2, 3]).subscribe(console.log);
// 从事件创建(浏览器环境)
// const clicks = fromEvent(document, 'click');
// clicks.subscribe(event => console.log(event));
// 定时器,只取前 3 个值
interval(1000).pipe(take(3)).subscribe(console.log);
管道操作符:转换和过滤流
操作符是 RxJS 的灵魂。它们通过 pipe 方法组合,每个操作符接收一个 Observable 并返回一个新的 Observable。
常用操作符:
map:转换每个值filter:过滤值debounceTime:防抖distinctUntilChanged:去除连续重复值switchMap:映射为新的 Observable,并取消前一个
示例:
import { from } from 'rxjs';
import { map, filter } from 'rxjs/operators';
from([1, 2, 3, 4, 5])
.pipe(
filter(n => n % 2 === 0),
map(n => n * 10)
)
.subscribe(console.log); // 20, 40
实战:实现实时搜索
假设我们有一个输入框,用户输入时触发搜索请求。我们需要:
- 监听输入事件
- 防抖,避免频繁请求
- 忽略空字符串
- 取消前一个未完成的请求,只保留最新结果
使用 RxJS 可以优雅地实现:
import { fromEvent, of } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, filter, switchMap, catchError } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';
// 模拟输入元素
const input = document.createElement('input');
document.body.appendChild(input);
const search$ = fromEvent(input, 'input').pipe(
map(event => event.target.value.trim()),
filter(query => query.length > 0),
debounceTime(300),
distinctUntilChanged(),
switchMap(query =>
ajax.getJSON(`/api/search?q=${query}`).pipe(
catchError(error => of({ error: true, message: error.message }))
)
)
);
search$.subscribe(result => {
console.log('搜索结果:', result);
});
关键点解析:
debounceTime(300):用户停止输入 300ms 后才发出值。distinctUntilChanged():如果新值与上一个值相同,则忽略。switchMap:将查询字符串映射为 HTTP 请求 Observable,并自动取消前一个请求,解决竞态问题。catchError:捕获错误,返回一个安全的 Observable,防止流中断。
> 💡 最佳实践:始终在 switchMap 内部处理错误,否则错误会传播并终止整个流。
常见坑与解决方案
1. 忘记取消订阅导致内存泄漏
在组件销毁时,必须取消订阅。可以使用 takeUntil 操作符:
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
const destroy$ = new Subject();
someObservable.pipe(takeUntil(destroy$)).subscribe(...);
// 组件销毁时
destroy$.next();
destroy$.complete();
2. 错误处理不当
如果不在管道内捕获错误,错误会直接抛给订阅者的 error 回调,并且流会终止。对于可能出错的请求,务必使用 catchError 返回一个备用 Observable。
3. 过度使用 RxJS
不是所有异步都需要 RxJS。简单的 Promise 链可能更直观。RxJS 适合处理多个事件源的组合、取消、时间控制等复杂场景。
与 React 集成
在 React 中,我们可以使用 useEffect 订阅流,并在清理函数中取消订阅。
import React, { useState, useEffect } from 'react';
import { fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged } from 'rxjs/operators';
function SearchBox() {
const [results, setResults] = useState([]);
useEffect(() => {
const input = document.getElementById('search');
const subscription = fromEvent(input, 'input')
.pipe(
map(e => e.target.value),
debounceTime(300),
distinctUntilChanged()
)
.subscribe(query => {
// 模拟搜索
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(setResults);
});
return () => subscription.unsubscribe();
}, []);
return (
<div>
<input id="search" placeholder="搜索..." />
<ul>
{results.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
总结
RxJS 提供了一种声明式的方式处理异步流,通过操作符可以轻松实现防抖、节流、竞态取消等复杂逻辑。本文通过实时搜索的示例,展示了核心概念和最佳实践。
下一步学习方向:
- 深入学习高阶操作符:
mergeMap、concatMap、exhaustMap的区别与使用场景。 - 掌握 Subjects(
Subject、BehaviorSubject、ReplaySubject)用于多播。 - 探索 RxJS 在状态管理(如 NgRx、Redux-Observable)中的应用。
希望这篇教程能帮助你迈入响应式编程的大门。
> 延伸阅读: > – RxJS 官方文档 > – Learn RxJS