【问题标题】:ChangeDetectionStrategy.OnPush and Observable.subscribe in Angular 2Angular 2 中的 ChangeDetectionStrategy.OnPush 和 Observable.subscribe
【发布时间】:2016-05-25 00:46:40
【问题描述】:

我正在尝试将 Observables 与 ChangeDetectionStrategy.OnPush 一起使用时的最佳实践。

该示例演示了想要显示某种加载消息(或者可能是简单的微调器动画)的常见场景:

Plnkr here

@Component({
  selector: 'my-app',
  template: `Are we loading?: {{loadingMessage}}`,

  // Obviously "Default" will notice the change in `loadingMessage`...
  // changeDetection: ChangeDetectionStrategy.Default

  // But what is best practice when using OnPush?
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class App implements OnInit {

  private loadingMessage = "Wait for it...";

  constructor() {

  }

  ngOnInit() {

    // Pretend we're loading data from a service.
    // This component is only interested in the call's success
    Observable.of(true)
      .delay(2000)
      .subscribe(success => {

        if(success){
          console.log('Pretend loading: success!');

          // OnPush won't detect this change
          this.loadingMessage = 'Success!';
        }

      });

  }
}

我或多或少地理解OnPush 对不变性的要求,至少对我而言,目前在谈论实际模型数据(可能保存在某种商店中)时它是有意义的。

所以,我有两个问题:

  1. 为什么分配新字符串值'Success!' 不会触发更改检测器?就不变性而言,价值已经改变了,对吧?
  2. 在使用ChangeDetectionStrategy.OnPush时应该如何实现轻量级内部组件状态(即loadingMessage)?如果有多种最佳做法,请指出正确的方向。

【问题讨论】:

    标签: javascript angular


    【解决方案1】:

    AFAIK,OnPush 在工作时使用directly with observables

    //our root app component
    import {Component, OnInit, ChangeDetectionStrategy} from 'angular2/core'
    import {Observable} from 'rxjs/Observable';
    import 'rxjs/Rx';
    
    @Component({
      selector: 'my-app',
      template: `Are we loading?: {{ loadingMessage |async }}`,
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class App implements OnInit {
      private loadingMessage;
      constructor() { }
    
      ngOnInit() {
        this.loadingMessage = Observable.of(true)
          .delay(2000)
      }
    }
    

    【讨论】:

    • 根据我的简化示例,这是一个很好的解决方案。实际上,返回Observable 的调用不是在ngOnInit 中进行的,而是在表单提交处理程序中进行的。这意味着模板绑定需要{{ loadingMessage && loadingMessage | async }}
    • ...此外,实际上,我使用单个属性state 维护内部状态,加载只是几种状态之一。我猜state 可能是另一个内部Observable,根据这个plnkr。随着时间的推移,可以将多个 state 值推送给它。
    • @PhilipBulley 另请注意,Angular2 表单具有内置的可观察对象(valueChanges 属性)。你可能也想调查一下……
    【解决方案2】:

    有了这个ChangeDetectionStrategy.OnPush,你告诉你的组件只监听它的输入属性的变化。

    我在您的示例中添加了加载组件,只是为了向您展示它是如何工作的: http://plnkr.co/edit/k5tkmcLlzkwz4t5ifqFD?p=preview

    【讨论】:

    • 感谢您的演示,Vlado。创建一个嵌套组件只是为了检测变化并不是一个很好的解决方案,但它确实很好地说明了你的观点。
    • 实际上,我不知道为什么loadingMessage 没有呈现为test,因为我们在最后setTimeout 中将值设置为test。有人可以帮我解释一下吗?
    【解决方案3】:

    好问题。我从 Savkin 那里得到两句关于 onPush 的引述(因为 Angular.io 文档似乎还没有关于这个主题的任何信息):

    仅当 OnPush 组件的输入发生更改或组件的模板发出事件时,框架才会检查这些组件。 -- ref

    当使用OnPush 时,Angular 只会在组件的任何输入属性更改、触发事件或可观察对象触发事件时检查组件。 -- ref(在评论回复@vivainio)

    第二个引用似乎更完整。 (太糟糕了,它被埋在评论中!)

    为什么新字符串值Success!的赋值没有触发 变化检测器?就不变性而言,值已经改变了,对吧?

    OnPush 不变性是指输入属性,而不是普通实例属性。如果loadingMessage 是输入属性并且值已更改,则将对组件执行更改检测。 (请参阅@Vlado 对 Plunker 的回答。)

    在使用ChangeDetectionStrategy.OnPush时应该如何实现轻量级内部组件状态(即loadingMessage)?如果有多个最佳实践,请指出正确的方向。

    这是我目前所知道的:

    • 如果我们更改内部状态作为处理事件的一部分,或作为可观察触发的一部分,更改检测将在OnPush 组件上执行(即,视图将更新)。在您的特定情况下,我对视图没有更新感到惊讶。这里有两个关于为什么不的猜测:
      • 添加delay() 使Angular 看起来更像setTimeout(),而不是可观察到的变化。 setTimeouts 不会导致在 OnPush 组件上执行更改检测。在您的示例中,更改检测器在 loadingMessage 的值更改前 2 秒完成了工作。
      • 就像@Sasxa 在他的回答中显示的那样,您必须在模板中绑定到Observable。即,也许它不仅仅是“可观察到的火灾”......也许它必须是一个 bound 可观察到的火灾。在这种情况下,创建loadingMessage(甚至更通用的state 属性)作为Observable 本身将允许您将模板绑定到它的值(或多个异步值),请参阅this example plnkr
        2016 年 4 月 28 日更新:看来绑定必须包括 | async,如 plnkr 和 this plnkr 中所示。
    • 如果我们改变内部状态并且它不是事件处理或可观察触发的一部分,我们可以将ChangeDetectorRef 注入我们的组件并调用方法markForCheck() 以使更改检测在OnPush 组件上执行并且所有祖先组件直到根组件。如果仅更改视图状态(即,组件及其后代的本地状态),则可以使用 detectChanges() 代替,它不会标记所有祖先组件以进行更改检测。 Plunker

    【讨论】:

    • 感谢您的全面回答。我认为最好的解决方案是markForCheck()(我对这是否有点hacky或可以接受)。或者创建一个loadingMessagestate 值作为Observable 本身,这样它就可以根据plnkr 表示多个状态。我想我会将其添加到您的答案中并接受,因为它似乎涵盖了所有基础。
    • @PhilipBulley, markForCheck() 不是 hacky。 ChangeDetectorRef API doc 显示了一个与您的用例非常相似的示例:具有OnPush 的组件具有在setTimeout() 回调中更新的numberOfTicks 属性,并调用markForCheck() 以确保视图更新。跨度>
    • 感谢您通过示例进行澄清。
    • @MarkRajcok 我在博客上找不到这句话'使用 OnPush 时,Angular 只会在组件的任何输入属性发生更改、触发事件或可观察对象触发事件时检查组件. -- 参考(在对@vivainio 的评论回复中)'?
    • @alt255,我也看不到了。
    猜你喜欢
    • 2017-02-09
    • 2017-12-03
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-31
    相关资源
    最近更新 更多