angular11源码探索二十四[路由检测变更策略]

NgZone

官网文档

https://angular.io/guide/zone

Angular为我们提供了NgZone服务,对于一些频繁的操作,可以不去触发变更检测。工作时优化性能

变更检测

  • Events - 一些事件,例如 clickchangeinputsubmit 等;
  • XMLHttpRequests - 网络请求;
  • Timers - setTimeout()setInterval() API 等;

每次变更检测都意味着额外的计算和资源消耗,如果我们需要对应用进行性能优化,那么首先该从这个概念下手。Angular 引入 Zone.js 以处理变更检测,具体来说,Zone.js 通过对所有常见的异步 API 打上了“补丁” 以追踪所有的异步操作,进而使 Angular 可以决定何时刷新 UI。

Zone.js

  • Zone 是一种用于拦截和跟踪异步工作的机制。
  • 在 Angular 应用中,每个 task 都会在 “Angular” Zone 中运行,这个 Zone 被称为 NgZone。一个 Angular 应用中只存在一个 Angular Zone,而变更检测只会由 运行于这个 NgZone 中的异步操作触发。

示例

函数runOutsideAngular 用于确保代码中在NgZone之外运行,保证Angular的变更检测不会因为相关代码而触发

setInterval 定时器便不会触发变更检测

export class OneComponent implements OnInit {
  constructor(
    private ngZone: NgZone
  ) {
    this.ngZone.runOutsideAngular(() => {
      setInterval(() => {
        ++this.num;
      }, 1000);
    });
  }

  num = 1;
}    

我们发现因为变更检测没有出发,所以视图没有更新

<h1>{{num}}</h1>

run 方法的目的与 runOutsideAngular 正好相反:任何写在 run 里的方法,都会进入 Angular Zone 的管辖范围

  this.ngZone.runOutsideAngular(() => {
      const token = setInterval(() => {
        this.ngZone.run(() => {
          ++this.num;
        });
        if (this.num == 10) {
          clearInterval(token);
        }
      }, 1000);
    });
  num = 1;

我们发现页面更新啦,

其实这是我在源码单元测试看到的,疑惑查了下,感觉在写大屏的时候可以使用的

如果我们有一个一定时间后更新一个值

 this.ngZone.runOutsideAngular(() => {
      const token = setTimeout(() => {
        this.ngZone.run(() => {
          this.num = 10;
        });
        clearTimeout(token);
      }, 5000);
    });

ApplicationRef

tick

调用此方法可显式处理更改检测及其副作用。

export class OneComponent implements OnInit {

  constructor(
    private ngZone: NgZone,
    private app: ApplicationRef
  ) {
    this.ngZone.runOutsideAngular(() => {
      const token = setTimeout(() => {
        this.num = 100;
          //调用更新检测
        app.tick();
        clearTimeout(token);
      }, 3000);
    });
  }
}

我们发现上面的案例更加适用于NgZone

那我们可不可以从脏依赖的角度上来看

@Component({
  selector: "hello",
  template: `
    <h1>Hello {{ name }}!</h1>
    <p>{{ counter }}</p>
    <button (click)="click()">click</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class HelloComponent {
  counter: number = 0;
  @Input() name: string;
  constructor(private app: ApplicationRef) {
    setInterval(() => this.counter++, 1000);
  }
  click() {
    this.app.tick();
  }
}

markForCheck()、OnPush、zone.js与detectChanges()

Angular 更新 HTML 的原理

  • 初始化 Component 。在最开始 bootstrapping Angular Application 的时候,Angular 会载入 bootstrap component 并调用 ApplicationRef.tick() 方法来触发 变更策略检测
  • 监听事件,如果DOM的事件更新了 Angular component 里面的data, 那么 变更策略检测 触发
  • HTTP 数据请求。如果在 Angular component 里更新了请求的数据
  • 宏任务微任务 ,方法里更新了 Angular component 的数据。比如 setTimeout(), setInterval()Promise.then() 里更新了 Angular component 的数据

zone.js

Angular 自带了 ngZone 的 service,这个 service 会创建一个叫 ‘angular’ 的 Zone。在这个 Angular Zone 里,当没有 schedule MicroTasks 或是当执行 sync/async function 的时候会自动触发 change detection。如果 Angular 的检测策略是的 default 的,那么所有的 asynchronous operations 都是在 Angular Zone 里,并且都会按以上条件自动触发 change detection。

不触发

this.ngZone.runOutsideAngular(() => {
      不触发
      });
this.ngZone.run(() => {
      触发
      });

detectChanges()

当这个第三方 APIs 的方法 update data 的时候,这个 update 是在 Angular Zone 之外的,所以 Angular 并不会知道这个 update 的发生,因此也不会触发变更策略检测。在这个情况下,就可以调用 ChangeDetectorRef.detectChanges() 来手动触发变更策略检测 也可以放在zone.run() 方法

关于 OnPushmarkForCheck()

如果 Angular 的检测策略被设置为 OnPush,那么只有以下条件下 change detection 才会被触发:

  • 当带有 @Input property 的引用完全变化或是完全被新的值替换的时候。
  • 当当前 component 或是 child component 触发 event 的时候,例如 Click,KeyUp,Subscription 等等。

很多时候 update 就不会触发变更策略检测。这时就必须通过调用 markForCheck() 来手动告诉 Angular 去检查并更新 view。所以总体来说,markForCheck() 基本上只在 Angular 的检测策略被设置为 OnPush 的时候会被需要使用到。

markForCheck()detectChanges() 的核心区别

最后,总结一下 markForCheck()detectChanges() 的核心区别。detectChanges() 会真正触发 Angular 的 change detection,而 markForCheck() 则不会。

当我们禁用NoopZone

Zone帮助Angular知道何时触发更改检测,并使开发人员专注于应用程序开发。默认情况下,Zone已加载且无需额外配置即可工作。但是,您不必一定要使用ZoneAngular。相反,您可以选择自己触发更改检测。

要删除Zone.js,请进行以下更改。

  1. zone.js从中删除导入polyfills.ts

    // import 'zone.js/dist/zone';  // Included with Angular CLI.`
    
  2. Bootstrap Angular的noop区域位于src/main.ts

    platformBrowserDynamic().bootstrapModule(AppModule, { ngZone: 'noop' })
      .catch(err => console.error(err));
    

思考

删除Zone.js 让我们获得更多性能优势,如果一切都是ChangeDetectionStrategy.OnPush

则永远不会触发更改检测周期,除非通过下面

  • ChangeDetectorRef.detectChanges()
  • ApplicationRef.tick()

注意一个易错点ChangeDetectorRef.markForCheck,它仅设置组件的视图层,而不会触发更改检测周期

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章