Angular2 变化检测策略

1.Angular 1 与 Angular 2 中的变化检测策略的区别


  • Angular2

      Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树,而每个组件都有自己的变化检测器,这意味着应用程序也是一颗变化检测器树。

      另外,Angular的数据流是自顶而下的,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测,尽管检查了父组件之后,子组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError。而在生产环境中,脏检查只会执行一次。

  • Angular1

          Angular 1所采用的变化检测原理是 – 脏检查:即存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应的视图。

          由于双向数据绑定的本质,在Angular 1中不能保证父节点在子节点之前总是被检查。 有可能子节点可以改变父节点或兄弟节点或树中的任何其他节点,这又会在链中触发新的更新。 这使得变化检测机制难以遍历所有节点,并可能掉入具有臭名昭着的“震荡”循环中:10 $digest() iterations reached. Aborting!
    【注:这是Angular 1中的当两个方法互相watch时,就会导致不停地进行digest循环,当循环大于十次Angular就会抛出如上错误。】

    Angular 1 与 Angular 2 中的变化检测策略的区别

二:数据何时变化


      主要是以下几种情况可能改变数据:

  • 用户输入操作,比如点击,提交等。
  • 请求服务端数据。
  • 定时事件,比如setTimeout,setInterval。
  • Promise(es6新加)

      这几点有个共同点,就是他们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。

三:变化检测策略


      1.Default

      默认情况下,Angular为我们的应用程序中的每个组件定义了一个默认的变化检测策略。我们可以使用@Component装饰器的changeDetection属性使这个定义显式的体现。

@Component({
  // ...
  changeDetection: ChangeDetectionStrategy.Default// 表示变化检测对象的状态为`CheckAlways`
}
export class SomeComponent {
  // ...
}

      Angular的默认变化检测策略,也就是上述提到的脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查。
      注意:对于一个对象,如果只改变其内部的属性,而不改变实例,则Angular检测不到变化(Angular比较的其实是引用),但是变化检测的默认策略还是会遍历树的所有组件

扫描二维码关注公众号,回复: 1113335 查看本文章

2.OnPush

       OnPush策略,就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。就是在你变化的时候才查你,如果不查你,你的子组件也不会查。这样就避免了无效的检测,很好的提高了性能。【我知道我没变,别查我】

@Component({
  // ...
  changeDetection: ChangeDetectionStrategy.OnPush// 表示变化检测对象的状态为`CheckOnce` 
}
export class SomeComponent {
  // ...
}

      OnPush策略,对于注入属性的依赖性比较高,那么相应的对注入属性的类型就有要求了,上面我们已经说到Angular比较的是引用,那么注入对象的时候,属性变化了我们都要去new一个新实例吗?这也太麻烦了吧。那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?那就是不可变对象(Immutable Object),下面我们看看注入属性的要求。

注入属性的类型要求:

  • 基本数据类型
  • 不可变对象(引入Immutable.js 库)
npm install --save immutable
  • 可观测对象.Obervable

          当然,在OnPush策略下,我们也可以手动控制变化监测。【我变了,只查我】我们可以通过引用变化检测对象 ChangeDetectorRef,可以手动去操作变化检测。我们可以在组件中通过依赖注入的方式来获取该对象:

constructor(private changeRef:ChangeDetectorRef){}

变化检测对象提供的方法有以下几种:

  • markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。
  • detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。(采用onPush策略之后的组件detach()无效)
  • reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测。
  • detectChanges() - 从该组件到各个子组件执行一次变化检测。
  • checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成。

      那么,如果输入属性是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测。例子如下:

@Component({
  templateUrl: 'test.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})

class TestCmp {
  @Input() obj:Observable<any>;
  public viewData:number = 0;
  constructor(private changeRef: ChangeDetectorRef) {}

  ngOnInit() {
    this.obj.subscribe(() => {//回调
      this.viewData++;  // 数据模型发生变化
      this.changeRef.markForCheck(); // 手动触发检测
    })
  }
}

四:总结


Angular与Angularjs都采用变化检测机制,前者优于后者主要体现在:

  1. 单项数据流动

  2. 以组件为单位维度独立进行检测

  3. 生产环境只进行一次检查

  4. 可自定义的变化检测策略:Default和onPush

  5. 可自定义的变化检测操作:markForcheck()、detectChanges()、detach()、reattach()、checkNoChanges()

五:相关资源


  1. https://www.jianshu.com/p/cee44e8831c9
  2. https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c
  3. https://blog.angularindepth.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
  4. https://vsavkin.com/immutability-vs-encapsulation-90549ab74487

猜你喜欢

转载自blog.csdn.net/try_try_try/article/details/80111985