【问题标题】:Prevent angular component from re-rendering unless data has changed除非数据已更改,否则防止角度组件重新渲染
【发布时间】:2021-09-13 05:23:47
【问题描述】:

我在连接到第三方 API PayPal BrainTree 的 Angular 组件时遇到问题,该 API 会动态地将 iframe 添加到组件主体。我需要保持连接和底层 html 持久,而不是与 api 断开连接。

有问题的组件与多个支付选项有关,其中一个选项连接到第三方 api。当另一个请求被触发以刷新支付方式时,就会出现问题,当这种情况发生时,支付选项会重新呈现,这会断开 API 并从组件中删除 iframe,然后再建立新的连接并创建新的 iframe。

Here is a working Minimum viable example for reference.

渲染的基础是

  1. 已发出 API 请求以获取付款选项
  2. 支付选项在循环中呈现
  3. 支付选项被渲染,LazyPaymentOptionComponent 和普通 PaymentOptionComponent 的组件不同
  4. 如果再次检查支付选项,整个组件将重新呈现,断开 API。
<div *ngFor="let option in payments$ | async">
  <app-lazy-payment-option *ngIf="option.lazy"></app-lazy-payment-option>
  <app-payment-option *ngIf="!option.lazy"></app-payment-option>
</div>

在 LazyPaymentOption 中,与第三方支付 API 的连接是在 ngAfterViewInit 期间建立的

@Component(...)
class LazyPaymentOptionComponent implements AfterViewInit {
  ngAfterViewInit(): void {
    this.thirdPartyService.connect().subscribe();
  }
}

理想情况下,我需要防止 LazyPaymentOption 破坏和重新渲染,在 React 中我会使用 shouldComponentUpdate 但我在 Angular 的组件级别没有找到类似的东西。

我找到了 ChangeDetectorRef,但我似乎无法阻止组件破坏和重新渲染,例如。我希望我可以使用this.cd.detatch() 来防止卸载和重新渲染组件。

@Component(...)
class LazyPaymentOptionComponent implements AfterViewInit {
  constructor(
    private cd: ChangeDetectorRef,
    private thirdPartyService: ThirdPartyService
  ) {}

  ngAfterViewInit(): void {
    this.thirdPartyService.connect().subscribe(() => {
      // attempt to detach from change detection so the component doesn't re-render
      this.cd.detatch();
    });
  }
}

在我手动实现之前,我能做些什么来阻止渲染循环吗?或者我应该尝试的其他任何事情,我已经深入研究了这个问题 2 天,但不确定我可以在哪里查看。

【问题讨论】:

    标签: angular typescript braintree


    【解决方案1】:

    您可以为此使用更改检测策略。

    @Component({
        selector: '...',
        changeDetection: ChangeDetectionStrategy.OnPush,
        template: `...`
    })
    export class ...Component  {
    

    默认的变更检测策略实际上会重新渲染很多组件。几乎每当发生可能改变您的一个 getter 值的事情时,它都会重新渲染。

    相比之下,OnPush 策略仅在以下情况下重新呈现

    • 您的组件的输入值实际上已更改
    • | async 管道具有新值

    由于这种行为,它也不会自动检测一些更改。 (例如,getter 的值已更改。)在这些情况下,您可能必须强制重新渲染它。

    constructor(private cdr: ChangeDetectorRef) {}
    
    onInit() {
      setInterval( () => {
        // this is something an OnPush strategy won't detect.
        this.counter++;
    
        // so you have to mark it for a rerender.
        this.cdr.markForCheck();
      }, 1000);
    }
    

    一般来说,出于性能原因,我尽量让我的大部分组件与OnPush 策略一起使用。

    根据经验,您最终可能会写很多 markForCheck。听起来事实并非如此。您应该更积极地使用异步管道。例如在前面的示例中,您可以将计数器建模为 BehaviorSubject

    public counter$ = new BehaviorSubject();   
    
    // note: in template file, use "counter$ | async"
    
    onInit() {
      let counter = 0;
      setInterval( () => {
        // this is something an OnPush strategy won't detect.
        counter++;
        this.counter$.next(counter);
      }, 1000);
    }
    

    从另一边看。每个setTimeout 实际上都可以重新渲染大量组件(具有默认更改检测策略的组件)。如果您事先知道它不需要重新渲染任何组件,您实际上也可以阻止它:

    constructor(private ngZone: NgZone)
    
    onInit() {
    
      // make the contained changes undetectable for angular.
      ngZone.runOutsideAngular(() => {
    
        setTimeout(() => {
          this.counter++;
        }, 1000);
    
      });
    }
    

    【讨论】:

    • 太棒了,我会试试你的建议。感谢您的帮助!
    • 不幸的是仍在重新渲染。似乎更改检测是在更高级别触发的,也许我可以在循环外的层次结构中将其移到更高的位置。你知道 Angular 有没有像 React 和 Vue 这样的虚拟 DOM 吗?
    • @synthet1c Angular 确实有一个虚拟 DOM。 ;话虽如此,在这种情况下,可能不仅是 LazyPaymentOptionComponent 需要使用 OnPush,而且可能是使用它的组件(包装它的组件)或围绕它的组件。您可能想暂时将所有这些都放在OnPush 策略上,然后随时重新启用重新渲染。
    • 我把它移到了包装payments.component.ts,它包含了支付方式的api请求和支付选项的循环。它仍然没有在那里工作。我必须深入研究文档才能准确了解OnPush 策略的工作原理。重新虚拟 DOM。我可以看到整个 PaymentsComponent 被销毁并重新渲染,而不仅仅是单个选项,因此它在计算 DOM 树更改时看起来不如 React 高效。
    • @synthet1c 是的,这就是 Angular 的特点。它试图通过自动启用“双向绑定”来简化事情。简单易学。不幸的是,2-way-binding 无法预测 getter 的结果。因此,一旦您开始在视图中引用 getter,它就会积极地重新渲染。然后OnPush 禁用它。但是,如果包装组件不使用OnPush,那么整个包装组件仍有可能被重新渲染。基本上,我建议也将 OnPush 放在 PaymentsComponent 和使用您的支付补偿的组件上。
    猜你喜欢
    • 1970-01-01
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2021-01-07
    • 2021-12-05
    • 2018-09-27
    • 2018-10-18
    • 1970-01-01
    相关资源
    最近更新 更多