除非数据已更改,否则防止重新渲染角度组件

时间:2021-07-01 06:11:31

标签: angular typescript braintree

我有一个角度组件问题,该组件连接到第三方 API PayPal BrainTree,该 API 动态地将 iframe 添加到组件主体。我需要保持连接和底层 html 持久化,而不是断开与 api 的连接。

有问题的组件与多个支付选项相关,其中一个选项连接到第三方 api。当另一个请求被触发以刷新支付方式时,问题就会出现,当这种情况发生时,支付选项会重新呈现,这会在建立新连接和创建新 iframe 之前断开 API 并从组件中删除 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 天,但不确定我可以在哪里查看。

1 个答案:

答案 0 :(得分: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);

  });
}