switchMap
switchMap 是另一个高阶映射操作符。它将源 Observable 发出的每个值映射成一个新的内部 Observable,但它只关心最新的那个。当源发出一个新值时,switchMap 会立即取消前一个内部 Observable 的订阅,并订阅新的内部 Observable。
核心行为:切换并取消
switchMap 的行为就像一个喜新厌旧的人。一旦有新的兴趣点(源值)出现,它会立即抛弃旧的(取消订阅),然后专注于新的。这使得它在处理那些"只关心最新结果"的场景中非常有用。
弹珠图
source: --a----b----c--|
map(v => --v1--v2|)
// a 对应的内部 Observable 在 b 出现时被取消
// b 对应的内部 Observable 在 c 出现时被取消
result: ----a1--b1--c1--c2|示例:Type-Ahead 搜索框
这是 switchMap 最经典的用例。当用户在搜索框中快速输入时,我们只关心最后一次输入对应的搜索结果,之前的搜索请求都应该被取消。
import { fromEvent } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';
const searchInput = document.getElementById('search');
const search$ = fromEvent(searchInput, 'input').pipe(
map(event => (event.target as HTMLInputElement).value),
debounceTime(300), // 防抖
distinctUntilChanged(), // 去重
switchMap(searchTerm =>
ajax.getJSON(`https://api.github.com/search/users?q=${searchTerm}`)
)
);
search$.subscribe(results => console.log(results));在这个流程中,如果用户在 300ms 内输入了新的字符,debounceTime 会阻止流继续。当用户停止输入后,switchMap 会发起一个 HTTP 请求。如果在该请求完成前,用户又输入了新的内容,switchMap 会自动取消前一个未完成的请求,并发起一个新的请求。这完美地避免了网络资源的浪费和处理过期的搜索结果。
何时使用?
当你只关心源 Observable 最新值所对应的结果时,switchMap 是不二之选。
- Type-ahead 搜索
- 取消操作:例如,一个"取消"按钮的点击流可以
switchMap到一个空的 Observable 来中断一个正在进行的操作。 - UI 交互:在一个可拖拽的元素上,
mousedown事件可以switchMap到mousemove事件流,并在mouseup时通过takeUntil停止。
警告
不要在那些不能被取消的写操作(如 POST, PUT, DELETE 请求)中使用 switchMap。因为如果源快速发出新值,switchMap 可能会取消一个已经发出去但尚未收到响应的写操作,导致你无法知道该操作是否在后端成功执行,从而造成数据不一致。在这种情况下,应该使用 mergeMap 或 concatMap。

