You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中Async管道为何比手动Subscribe渲染响应更慢

为什么手动Subscribe实现比Async管道版本的响应性更好?

我开发了一款基于Observable实现的员工搜索应用,逻辑十分简单,以下为两个版本的实现对比:

手动Subscribe版本

模板代码

<div class="row row-cols-1 row-cols-md-6 g-2">
    <ng-container *ngIf="emps.length">
        <div class="col" *ngFor="let emp of emps">
            <div class="card h-100">
                <img *ngIf="emp.image" [src]="emp.image" class="card-img-top" alt="image">
                <div class="card-body">
                    <span *ngIf="emp.actived" class="badge rounded-pill bg-success"
                            style="position: absolute;top: 2px;right: 2px;">&#10003;
                    </span>
                    <h5 class="card-title">{{emp.name| uppercase}}</h5>
                    <!-- <pre class="card-text">{{emp.phones}}</pre> -->
                </div>
            </div>
        </div>
    </ng-container> 
</div>

组件代码

emps: IToList[] = [];

change(){
  this.service.getPeople(this.searchTerm).pipe(
     map(data => data.map(d => {
         let c = new Person(d._id, d.actived, d.name, d.phones, d.image);
         return c.getPerson();
     }))
  ).subscribe(data=>this.emps=data);
}

该版本运行流畅、响应及时,运行效果可参考对应演示动图。


Async管道版本

模板代码

<div class="row row-cols-1 row-cols-md-6 g-2">
    <ng-container *ngIf="people$|async as emps">
        <div class="col" *ngFor="let emp of emps">
            <div class="card h-100">
                <img *ngIf="emp.image" [src]="emp.image" class="card-img-top" alt="image">
                <div class="card-body">
                    <span *ngIf="emp.actived" class="badge rounded-pill bg-success"
                            style="position: absolute;top: 2px;right: 2px;">&#10003;
                    </span>
                    <h5 class="card-title">{{emp.name | uppercase}}</h5>
                    <!-- <pre class="card-text">{{emp.phones}}</pre> -->
                </div>
            </div>
        </div>
    </ng-container> 
</div>

组件代码

people$!: Observable<IToList[]>;
      
change(){
   this.people$ = this.service.getPeople(this.searchTerm).pipe(
      map(data => data.map(d => {
           let c = new Person(d._id, d.attivo, d.nominativo, d.telefoni, d.immagine);
           return c.getPerson();
      }))
  ); 
}

该版本运行时响应明显卡顿,运行效果可参考对应演示动图。


问题补充(2022年6月10日更新)

  • 卡片中渲染的图片src为base64字符串,单张大小为3MB。

原因分析

卡顿和Async管道本身的性能没有任何关系,纯粹是写法差异叠加超大base64图片的渲染开销导致的,核心差异点有两个:

  1. 更新触发的DOM操作量差了一倍
    手动subscribe版本只在接口返回数据的那一刻给emps赋值,Angular只会在数据返回后跑一次变更检测,直接渲染列表,中间没有多余的DOM操作。
    你写的Async版本每次触发change()就给people$赋一个全新的Observable对象。Async管道只要检测到传入的Observable引用变化,会立刻执行三个动作:取消旧Observable的订阅、把当前持有的渲染值清空为null、订阅新的Observable。这时候Angular检测到people$|async的结果变成null,会先把整个已经渲染好的员工列表DOM全部删除;等新Observable的接口数据返回,Async管道拿到新数据,Angular又要从头创建所有卡片DOM,等于每次搜索都要多跑一轮「删除全部旧DOM→重建全部新DOM」的操作,平白多了一倍的DOM开销。
  2. 3MB的base64大图把开销放大到了可感知卡顿的程度
    单张3MB的base64图片,从DOM挂载、格式解码到绘制到页面上,每一步都要占用主线程资源,还要消耗大量内存。手动subscribe版本里,你直接替换emps数组的引用,*ngFor默认按对象引用跟踪列表项,如果两次搜索结果里包含同一个员工,对应的DOM节点会被直接复用,连图片都不需要重新解码绘制。但你写的Async版本每次都先把旧DOM全部删除,浏览器要先回收这些大图片占用的内存,等新DOM创建的时候又要重新解码、重新绘制所有图片,主线程直接被占满,自然会出现明显卡顿。

优化方案

  • 不要每次搜索都给people$重新赋值,改用Subject推送搜索关键词,在组件初始化阶段就定义好Observable流,只让Async管道订阅一次:
private searchTerm$ = new Subject<string>();
people$!: Observable<IToList[]>;

ngOnInit() {
  this.people$ = this.searchTerm$.pipe(
    debounceTime(300), // 加防抖避免输入时频繁发请求
    distinctUntilChanged(), // 搜索词没变化就不重复发请求
    switchMap(term => this.service.getPeople(term).pipe(
      map(data => data.map(d => {
        const c = new Person(d._id, d.actived, d.name, d.phones, d.image);
        return c.getPerson();
      }))
    ))
  );
}

change() {
  this.searchTerm$.next(this.searchTerm);
}
  • 给*ngFor添加自定义trackBy函数,按员工唯一ID跟踪列表项,就算数据更新也复用已有DOM节点,避免重复销毁重建:
<div class="col" *ngFor="let emp of emps; trackBy: trackByEmpId">
trackByEmpId(index: number, emp: IToList) {
  return emp._id;
}
  • 不要直接用3MB的base64字符串作为图片源,尽量压缩图片体积、转成WebP等高效格式,配合图片懒加载减少主线程渲染压力。

内容的提问来源于stack exchange,提问作者Kraken

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 05:12:23