Angular:使用单个 Rx 运算符进行异步渲染

2025-06-08

Angular:使用单个 Rx 运算符进行异步渲染

这篇文章最初发表于Angular Bites

按照我的意思,异步渲染的概念很简单:在屏幕上渲染项目的过程是分散的,这样浏览器就不会阻塞,直到所有项目都渲染完毕。

它的工作原理如下:渲染第一个项目,然后稍等片刻,再渲染下一个项目,依此类推。在此期间,浏览器可以执行循环中所有其他预定的事件,然后再让它再次渲染。

何时以及为何应该使用它,有时

这在什么时候尤其有效?

  • 如果我们要渲染特别长且繁重的列表
  • 如果列表中的每个项目占用大量页面空间

为什么?因为你的应用看起来会更快。它实际上并不会更快,但你的用户会感觉到它更快。这就足​​够了。

单一操作员方法

过去我曾用各种方法解决过这个问题,正如我在如何在 Angular 中渲染大型列表中所描述的那样。

这次我想到了一个单一的操作符,它将按顺序分散数组子集的渲染过程。

我们称这个运算符为lazyArray。它支持两个参数:

  • delayMs= 浏览器渲染下一个数组之前应等待多长时间
  • concurrency= 一次渲染多少个项目

只要给我看代码就行了,Giancarlo!

好的,就是这样:

export function lazyArray<T>(
  delayMs = 0,
  concurrency = 2
) {
  let isFirstEmission = true;

  return (source$: Observable<T[]>) => {
    return source$.pipe(
      mergeMap((items) => {
        if (!isFirstEmission) {
          return of(items);
        }

        const items$ = from(items);

        return items$.pipe(
          bufferCount(concurrency),
          concatMap((value, index) => {
            const delayed = delay(index * delayMs);

            return scheduled(of(value), animationFrameScheduler).pipe(delayed);
          }),
          scan((acc: T[], steps: T[]) => {
            return [ ...acc, ...steps ];
          }, []),
          tap((scannedItems: T[]) => {
            const scanDidComplete = scannedItems.length === items.length;

            if (scanDidComplete) {
              isFirstEmission = false;
            }
          }),
        );
      }),
    );
  };
}
Enter fullscreen mode Exit fullscreen mode

用法

使用它非常简单,就像使用其他运算符一样:

@Component({ ... })
export class MyComponent {
   items$ = this.service.items$.pipe(
     lazyArray()
   );
}
Enter fullscreen mode Exit fullscreen mode

让我们来分析一下,好吗?

我们希望跟踪它是否是第一次发射。我们只想在第一次进行延迟渲染:

let isFirstEmission = true;
Enter fullscreen mode Exit fullscreen mode

我们将数组转换为项目流:

const items$ = from(items);
Enter fullscreen mode Exit fullscreen mode

我们根据并发性将项目数量收集到一个数组中:

bufferCount(concurrency),
Enter fullscreen mode Exit fullscreen mode

我们根据延迟安排渲染,然后根据项目的索引逐步增加延迟:

concatMap((value, index) => {
  const delayed = delay(index * delayMs);

  return scheduled(of(value), animationFrameScheduler).pipe(delayed);
})
Enter fullscreen mode Exit fullscreen mode

我们不断将处理过的项目收集到一个数组中:

scan((acc: T[], steps: T[]) => {
  return [ ...acc, ...steps ];
}, [])
Enter fullscreen mode Exit fullscreen mode

最后,我们检查已处理项目的数量是否与初始列表一样长。通过这种方式,我们可以了解第一次发射是否完成,以及如果我们将标志设置为false

tap((scannedItems: T[]) => {
  const scanDidComplete = scannedItems.length === items.length;

  if (scanDidComplete) {
    isFirstEmission = false;
  }
})
Enter fullscreen mode Exit fullscreen mode

演示

我之所以想到这个,是因为我的应用程序 Formtoro 在启动时会加载大量数据,从而一次性渲染大量 Stencil 组件。

它运行得不太好,卡顿很严重。我不太喜欢它,所以我找到了解决办法。我来给你展示一下区别:

不带lazyArray运算符:

不使用惰性数组

lazyArray运算符:

使用惰性数组


这种方法对我来说非常有效,但对你来说可能就不行了。如果你需要帮助,可以给我发邮件。

再见!


如果你喜欢这篇文章,请在Twitter上关注我,或者查看我的新博客Angular Bites

鏂囩珷鏉ユ簮锛�https://dev.to/angular/angular-async-rendering-with-a-single-rx-operator-4d6d
PREV
Angular 安全检查表
NEXT
Angular + Animate.css 的五个简单步骤