switchMap、mergeMap 和 concatMap 都是 RxJS 操作符,用于处理 Observable 的嵌套订阅,但它们的行为在处理 多个内部订阅 时有所不同:
1️⃣ switchMap
-
特点:切换到新的 Observable,取消之前的订阅。
-
适用场景:适合搜索、输入框联想查询等,只关心最新的请求,避免并发。
-
示例:
fromEvent(inputElement, 'input').pipe( switchMap(event => http.get(`/search?q=${event.target.value}`)) ).subscribe(console.log);行为:
- 用户不断输入时,之前的请求会被取消,只保留最新的请求。
2️⃣ mergeMap
-
特点:同时处理多个内部 Observable,不会取消之前的订阅。
-
适用场景:适合并发请求、批量处理任务等,不希望丢弃任何数据流。
-
示例:
from([1, 2, 3]).pipe( mergeMap(id => http.get(`/user/${id}`)) ).subscribe(console.log);行为:
1, 2, 3的请求会并行执行,不会等待前一个完成。
3️⃣ concatMap
-
特点:按顺序执行,前一个完成后才执行下一个,类似
mergeMap+concat。 -
适用场景:适合需要按顺序执行的请求,如批量上传、事务处理等。
-
示例:
from([1, 2, 3]).pipe( concatMap(id => http.get(`/user/${id}`)) ).subscribe(console.log);行为:
1的请求完成后,才会执行2,然后再执行3。
🔥 总结
| 操作符 | 是否并发 | 是否取消前一个 | 适用场景 |
|---|---|---|---|
switchMap | ❌(只保留最新) | ✅ | 取消无用的请求(搜索、输入框) |
mergeMap | ✅(并行) | ❌ | 并发请求(批量 API 调用) |
concatMap | ❌(顺序执行) | ❌ | 按顺序执行(事务、上传) |
如果任务可以并行执行,就用 mergeMap;
如果需要保证顺序,就用 concatMap;
如果只关心最新的请求,就用 switchMap。
除了 switchMap、mergeMap 和 concatMap,还有以下RxJS 操作符:
1️⃣ map(映射数据)
作用:对流中的每个值进行转换(同步转换)。
常见场景:修改 API 返回的数据格式。
of(1, 2, 3).pipe(
map(x => x * 10)
).subscribe(console.log); // 输出 10, 20, 30
2️⃣ filter(过滤数据)
作用:筛选符合条件的值。
常见场景:表单验证、筛选列表数据。
of(1, 2, 3, 4, 5).pipe(
filter(x => x % 2 === 0)
).subscribe(console.log); // 输出 2, 4
3️⃣ debounceTime(防抖)
作用:延迟一定时间后再发出最新值,防止频繁触发。
常见场景:搜索框输入,只在用户停止输入后触发请求。
fromEvent(inputElement, 'input').pipe(
debounceTime(300),
map(event => event.target.value)
).subscribe(console.log);
4️⃣ throttleTime(节流)
作用:在一定时间内只执行一次,忽略期间的其他事件。
常见场景:按钮防抖(防止用户短时间内多次点击)。
fromEvent(button, 'click').pipe(
throttleTime(1000)
).subscribe(() => console.log('Clicked!'));
5️⃣ distinctUntilChanged(去重)
作用:当值变化时才触发,避免相同值重复触发。
常见场景:搜索优化,防止相同输入重复触发请求。
fromEvent(inputElement, 'input').pipe(
map(event => event.target.value),
distinctUntilChanged()
).subscribe(console.log);
6️⃣ take & takeUntil(控制流的结束)
take(n): 只取前n个值后完成。takeUntil(stop$): 当stop$触发时结束流。
常见场景:只监听前 n 次事件 或 手动控制订阅何时停止。
interval(1000).pipe(
take(3) // 只取 3 次后结束
).subscribe(console.log); // 输出 0, 1, 2
const stop$ = fromEvent(button, 'click');
interval(1000).pipe(
takeUntil(stop$) // 当按钮点击时结束
).subscribe(console.log);
7️⃣ combineLatest(合并多个流)
作用:当任意一个流发生变化时,返回所有流的最新值。
常见场景:动态表单的多个输入框,需要计算结果。
combineLatest([
fromEvent(input1, 'input').pipe(map(e => e.target.value)),
fromEvent(input2, 'input').pipe(map(e => e.target.value))
]).subscribe(([val1, val2]) => console.log(val1, val2));
8️⃣ forkJoin(等待所有请求完成)
作用:所有流都完成后才发出最终值(适用于并行请求)。
常见场景:加载多个 API,等数据都返回后再渲染页面。
forkJoin({
user: http.get('/user'),
posts: http.get('/posts')
}).subscribe(result => console.log(result));
📌 区别:combineLatest 是任意一个流变动都会触发,而 forkJoin 只在所有流完成时触发一次。
💡 总结
| 操作符 | 作用 | 常见场景 |
|---|---|---|
map | 同步转换数据 | API 数据转换 |
filter | 过滤数据 | 列表筛选 |
debounceTime | 防抖 | 输入框搜索 |
throttleTime | 节流 | 按钮防抖 |
distinctUntilChanged | 去重 | 避免重复 API 请求 |
take / takeUntil | 结束流 | 监听有限次数 / 手动停止订阅 |
combineLatest | 合并多个流(任何变化都会触发) | 计算多个输入框的值 |
forkJoin | 等待所有请求完成后再触发 | 多个 API 并行加载 |