详解Angular中的变更检测(五)- NgZone 2

230 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

NgZone run() 和 runOutsideOfAngular()

Zone 处理大多数异步 API,比如 setTimeout()、Promise.then() 和 addEventListener()。有关完整列表,请参见 Zone 模块的文档。因此,在这些异步 API 中,你无需手动触发变更检测。

仍然有一些 Zone 无法处理的第三方 API。在这种情况下,NgZone 服务提供了 run() 方法,该方法允许你在 angular Zone 中执行函数。此函数以及该函数中的所有异步操作会在正确的时间自动触发变更检测。

export class AppComponent implements OnInit {
  constructor(private ngZone: NgZone) {}
  ngOnInit() {
    // New async API is not handled by Zone, so you need to use ngZone.run()
    // to make the asynchronous operation callback in the Angular zone and
    // trigger change detection automatically.
    someNewAsyncAPI(() => {
      this.ngZone.run(() => {
        // update the data of the component
      });
    });
  }
}

默认情况下,所有异步操作都在 Angular Zone 内,这会自动触发变更检测。另一个常见的情况是你不想触发变更检测。在这种情况下,你可以使用另一个 NgZone 方法:runOutsideAngular()

export class AppComponent implements OnInit {
  constructor(private ngZone: NgZone) {}
  ngOnInit() {
    // You know no data will be updated,
    // so you don't want to trigger change detection in this
    // specified operation. Instead, call ngZone.runOutsideAngular()
    this.ngZone.runOutsideAngular(() => {
      setTimeout(() => {
        // update component data
        // but don't trigger change detection.
      });
    });
  }
}

思考一下,实际开发工作中,什么场景下不需要触发变更检测?后面会有实际的例子。

设置Zone.js

为了使 Zone.js 在 Angular 中可用,你需要导入 zone.js 包。

如果使用的是 Angular CLI,则此步骤将自动完成,并且你会在 polyfills中看到以下行:

在导入 zone.js ****软件包之前,你可以做如下配置:

  • 你可以禁用一些异步 API 的猴子补丁,以获得更好的性能。比如,你可以禁用 requestAnimationFrame() 的猴子补丁,这样 requestAnimationFrame() 的回调就不会触发变更检测。如果你的应用程序不会在 requestAnimationFrame() 回调中更新任何数据,则这种方式很有用。
  • 你可以指定某些 DOM 事件不在 Angular Zone 内运行;比如,为了防止 mousemove ****或 scroll ****事件来触发变更检测。

你还可以更改另外几个设置。要进行这些更改,你需要创建一个 zone-flags.ts 文件,如下所示。

// disable patching requestAnimationFrame
(window as any).__Zone_disable_requestAnimationFrame = true;

// disable patching specified eventNames
(window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove'];

接着,在 polyfills.ts 中导入 zone.js 之前先导入 zone-flags:

/***************************************************************************************************
 * Zone JS is required by default for Angular.
 */
import `./zone-flags`;
import 'zone.js/dist/zone';  // Included with Angular CLI.

有关可以配置的内容的更多信息,请参阅Zone.js文档。

移除zone.js

默认情况下,Zone 已加载且无需其他配置即可工作,Angular 可以自动帮我们进行变更检测,这主要是基于Zone.js来实现,但是zone.js并不是Angular的不可缺少一部分,Angular也不必一定要基于zone.js,也就是没有zone.js Angular也可以工作。如果我们对应用有极高的性能要求时,我们可以选择移除zone.js,移除zone.js将会提升应用的性能和打包的体积,但是我们必须主动去调用变更检测。

如果禁用了 Zone ,你就要自己在正确的时间触发所有变更检测,这需要你对变更检测机制有全面的了解

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

  1. 从 polyfills.ts 中移除对 zone.js 的导入;
  2. 在 src/index.ts 中使用 noop Zone 启动 Angular:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
...

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

当移除zone.js后,需要我们手动调用变更检测。

...
constructor(private cdr: ChangeDetectorRef) {}

ngOnInit(): void {
   this.ob$.subscribe((data) => {
     ......
     this._cd.detectChanges();
   });
}

...

思考:除了上面的方式,还可以用哪些方式手动触发变更检测呢?

后面的文章继续详细讲解变更检测是如何工作的。