map
是最基本的转换操作符,用于对 Observable 发出的每个值进行一对一转换。
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
// 示例1:简单数值转换
of(1, 2, 3).pipe(
map(x => x * 2)
).subscribe(result => console.log(result));
// 输出: 2, 4, 6
// 示例2:对象属性转换
const users = of(
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 }
);
users.pipe(
map(user => ({
...user,
name: user.name.toUpperCase(),
isAdult: user.age >= 18
}))
).subscribe(console.log);
/* 输出:
{id: 1, name: "ALICE", age: 25, isAdult: true}
{id: 2, name: "BOB", age: 30, isAdult: true}
*/
// 示例3:API响应处理
import { from } from 'rxjs';
function fetchUser(userId) {
return from(
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
.then(response => response.json())
);
}
fetchUser(1).pipe(
map(user => user.name)
).subscribe(name => console.log('用户名:', name));
// 输出: 用户名: Leanne Graham
mergeMap
用于将一个值映射成一个新的 Observable,并同时订阅所有这些内部 Observable,合并它们的输出。
import { of, interval } from 'rxjs';
import { mergeMap, take } from 'rxjs/operators';
// 示例1:将每个值转换为新的Observable
of('a', 'b', 'c').pipe(
mergeMap(letter =>
interval(1000).pipe(
take(3),
map(i => letter + i)
)
)
).subscribe(console.log);
/* 输出 (每秒一个):
a0
b0
c0
a1
b1
c1
a2
b2
c2
*/
// 示例2:实际API请求
function fetchPosts(userId) {
return from(
fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
.then(response => response.json())
);
}
of(1, 2, 3).pipe(
mergeMap(userId => fetchPosts(userId))
).subscribe(posts => {
console.log(`用户${posts[0]?.userId}的帖子数:`, posts.length);
});
/* 输出:
用户1的帖子数: 10
用户2的帖子数: 10
用户3的帖子数: 10
*/
// 示例3:结合Promise使用
function uploadFile(file) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`文件${file.name}上传成功`);
}, 1000);
});
}
const files = of(
{ name: 'document.pdf' },
{ name: 'image.jpg' }
);
files.pipe(
mergeMap(file => from(uploadFile(file)))
).subscribe(result => console.log(result));
/* 输出 (大约同时):
文件document.pdf上传成功
文件image.jpg上传成功
*/
switchMap
会在每次发出新值时取消之前的内部 Observable 订阅,并订阅新的 Observable。
import { fromEvent, interval } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
// 示例1:基础用法
fromEvent(document.getElementById('searchInput'), 'input').pipe(
switchMap(event => {
const query = event.target.value;
return from(
fetch(`https://api.example.com/search?q=${query}`)
.then(res => res.json())
);
})
).subscribe(results => {
console.log('搜索结果:', results);
});
// 示例2:与interval结合
const source$ = of('start').pipe(
switchMap(() => interval(1000).pipe(
take(5),
map(i => `值 ${i}`)
))
);
source$.subscribe(console.log);
/* 输出 (每秒一个):
值 0
值 1
值 2
值 3
值 4
*/
// 示例3:实际应用 - 取消之前的请求
function searchBooks(query) {
return from(
fetch(`https://openlibrary.org/search.json?q=${query}`)
.then(res => res.json())
);
}
const searchInput = document.getElementById('bookSearch');
fromEvent(searchInput, 'input').pipe(
map(event => event.target.value),
filter(query => query.length > 2), // 至少3个字符才搜索
debounceTime(300), // 防抖300ms
distinctUntilChanged(), // 值变化才继续
switchMap(query => searchBooks(query))
).subscribe(results => {
console.log('找到书籍:', results.docs);
});
tap
用于在 Observable 流中执行副作用操作,不影响数据流。
import { of } from 'rxjs';
import { tap, map } from 'rxjs/operators';
// 示例1:基本调试
of(1, 2, 3).pipe(
tap(value => console.log('收到原始值:', value)),
map(x => x * 2),
tap(value => console.log('转换后的值:', value))
).subscribe();
/* 输出:
收到原始值: 1
转换后的值: 2
收到原始值: 2
转换后的值: 4
收到原始值: 3
转换后的值: 6
*/
// 示例2:实际应用 - 记录API请求
function getUser(userId) {
return from(
fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
).pipe(
tap(response => {
console.log(`请求用户${userId}的状态:`, response.status);
if (!response.ok) {
throw new Error('请求失败');
}
}),
switchMap(response => from(response.json()))
);
}
getUser(1).pipe(
tap(user => {
console.log('成功获取用户:', user.name);
// 可以在这里更新UI状态
document.getElementById('loading').style.display = 'none';
}),
catchError(error => {
console.error('获取用户失败:', error);
return of(null);
})
).subscribe();
// 示例3:状态管理
let requestCount = 0;
function fetchData() {
return from(fetch('/api/data')).pipe(
tap(() => {
requestCount++;
console.log(`当前请求数: ${requestCount}`);
document.getElementById('spinner').style.display = 'block';
}),
switchMap(response => response.json()),
tap(() => {
document.getElementById('spinner').style.display = 'none';
})
);
}
import { of, fromEvent } from 'rxjs';
import { map, mergeMap, switchMap, tap, delay } from 'rxjs/operators';
// 模拟API函数
function simulateApi(id, delayTime) {
return of(`结果 ${id}`).pipe(delay(delayTime));
}
// 对比mergeMap和switchMap
const button = document.getElementById('myButton');
fromEvent(button, 'click').pipe(
map((_, index) => index + 1), // 点击次数
tap(count => console.log(`点击 #${count}`)),
// 尝试切换这两个操作符查看区别
// mergeMap(count => simulateApi(count, 1000))
switchMap(count => simulateApi(count, 1000))
).subscribe(result => console.log('收到:', result));
/*
使用mergeMap时的可能输出:
点击 #1
点击 #2
点击 #3
收到: 结果 1
收到: 结果 2
收到: 结果 3
使用switchMap时的可能输出:
点击 #1
点击 #2 (在1秒内点击)
点击 #3 (在1秒内点击)
收到: 结果 3 (只收到最后一个结果)
*/
map:
mergeMap:
switchMap:
tap:
记住选择操作符的关键:
mergeMap
switchMap
map
tap
在 RxJS 的 pipe
中,操作符的执行顺序是从上到下、从左到右的线性执行流程。让我们通过你提到的 map
、tap
和 switchMap
组合来分析它们的执行顺序。
map
、tap
):立即执行switchMap
):会创建新的 Observableimport { fromEvent } from 'rxjs';
import { map, tap, switchMap } from 'rxjs/operators';
fromEvent(document.getElementById('searchInput'), 'input').pipe(
tap(event => console.log('1. 原始事件:', event)), // 第1步
map(event => event.target.value), // 第2步
tap(value => console.log('2. 提取的值:', value)), // 第3步
switchMap(query => fetch(`/api/search?q=${query}`)) // 第4步
).subscribe(response => {
console.log('5. 最终结果:', response); // 第5步
});
假设用户在搜索输入框中输入 “rxjs”:
tap(event => console.log('1. 原始事件:', event))
map(event => event.target.value)
event
转换为字符串值(如 “rxjs”)tap(value => console.log('2. 提取的值:', value))
switchMap(query => fetch(
/api/search?q=${query}))
subscribe 回调
输入事件 → tap(记录原始事件) → map(提取值) → tap(记录值) → switchMap(发起请求) → 订阅接收响应
switchMap
的特殊行为:
tap
的位置影响:
// 示例1:tap在switchMap之前
.pipe(
tap(x => console.log('before switchMap', x)),
switchMap(x => fetchData(x))
)
// 示例2:tap在switchMap之后
.pipe(
switchMap(x => fetchData(x)),
tap(x => console.log('after switchMap', x))
)
错误处理:
switchMap
内部的 Observable 出错,错误会跳过后续操作符直接到达 subscribe 的 error 回调假设我们有一个搜索框,用户依次输入:r → rx → rxj → rxjs
fromEvent(searchInput, 'input').pipe(
debounceTime(300),
map(event => event.target.value),
tap(query => console.log(`正在处理查询: ${query}`)),
switchMap(query =>
from(fetch(`/api/search?q=${query}`).then(res => res.json()))
).subscribe({
next: results => console.log('搜索结果:', results),
error: err => console.error('搜索失败:', err)
});
可能的执行过程:
用户输入 ‘r’ → 300ms 没有新输入 → 触发搜索
用户在 300ms 内继续输入 ‘rx’ → 取消 ‘r’ 的请求
用户继续快速输入 ‘rxj’ → 取消 ‘rx’ 的请求
用户最后输入 ‘rxjs’ 并停止 300ms → 取消 ‘rxj’ 的请求