深入Angular:(转&翻)Do you still think that NgZone (zone.js) is required for change detection in Angular?

原文链接:IndepthApp

前言概览:注意区分NgZone和zone.js, 更多细节在Angular跟新策略篇,尚未翻译完成。

Do you still think that NgZone (zone.js) is required for change detection in Angular?

abstract:This article explains why and how Angular implements NgZone over zone.js library. Read to learn how the framework can be used completely without zone.js and when automatic change detection can fail.

本文主要解释了Angular是如何基于zone.js实现NgZone。 同时阐述如何在不使用zone.js的情况下,实现手动更新。文章最后部分将描述自动跟新策略何时会失效。

我看过的大多数文章都将Zone(zone.js)和NgZone与Angular中的变更检测紧密关联在一起。尽管它们确实有关系,但从技术上讲,它们并不是一个整体的部分。是的,Zone和NgZone用于自动触发由异步操作引起的变更检测。但由于变更检测是一个单独的机制,它可以在没有Zone和NgZone的情况下成功工作。在第一章中,我将展示如何在没有zone.js的情况下使用Angular。文章的第二部分解释了Angular和zone.js如何通过NgZone进行互动。最后,我还会展示为什么自动变更检测有时无法与诸如Google API客户端库(gapi)等第三方库一起使用。

我已经撰写了许多关于Angular中变更检测的详细文章。如果您想全面了解变更检测的工作原理,我建议从阅读所有这些文章开始,其中包括本文。这5篇文章将使您成为Angular变更检测的专家。请注意,本文不涉及zone.js,而是关于Angular如何在实现NgZone时使用区域以及它与变更检测机制的关系。要了解更多关于区域的信息,请阅读我的Reverse-engineered Zones (zone.js)文章,其中包含了我所发现的内容。(IndepthApp)

Using Angular without Zone (zone.js) 在没有Zone.js的情况下使用NG

为了证明Angular可以在没有Zone的情况下成功工作,我最初计划提供一个模拟的区域对象,它什么也不做。但是,即将发布的Angular 5版本为我简化了这些事情。现在,通过配置方式,它提供了一种使用不执行任何操作的noop Zone的方法。

feat(core): support for bootstrap with custom zone (#17672) · angular/angular@344a5ca · GitHub

为了做到这一点,首先让我们删除对zone.js的依赖。我将使用StackBlitz来演示应用程序,并且由于它使用Angular-CLI,所以我将从polyfills.ts文件中删除以下导入语句:

* Zone JS is required by Angular itself. */
import 'zone.js/dist/zone';  // Included with Angular CLI.

之后,我将像这样配置Angular来使用noop Zone的实现:

platformBrowserDynamic()
    .bootstrapModule(AppModule, {
        ngZone: 'noop'
    });

如果您现在运行该应用程序(Angular (forked) - StackBlitz),您将看到更改检测是完全可操作的,并在DOM中呈现name组件属性。

现在尝试使用 setTimeout() 来更新属性:

export class AppComponent  {
    name = 'Angular 4';
 
    constructor() {
        setTimeout(() => {
            this.name = 'updated';
        }, 1000);
    }

很明显没有触发更新,由于在前置配置中把ngZone设为 'noop'。但是我可以获取组件的引用(ApplicationRef )的 tick( ) 方法来手动触发更新。

export class AppComponent  {
    name = 'Angular 4';
 
    constructor(app: ApplicationRef) {
        setTimeout(()=>{
            this.name = 'updated';
            app.tick();
        }, 1000);
    }

好的更新成功。代码案例:Angular (forked) - StackBlitz

总结下来, 上面演示的重点是向你展示zone.js和NgZone并不是变更检测实现的一部分。这是一种非常方便的机制,可以通过调用app.tick()来自动触发变更检测,而不是在某些时机手动执行。我们一会儿会看到这些时机是什么。

How NgZone uses Zones 了解NgZone是如何包装Zone.js的

此部分将演示了Zone提供的两个功能——上下文传播出色的异步任务跟踪。Angular实现的NgZone类在很大程度上依赖于任务跟踪机制。

NgZone只是包装了zone.js

function forkInnerZoneWithAngularBehavior(zone: NgZonePrivate) {
    zone._inner = zone._inner.fork({
        name: 'angular',
        ...

zone.js的部分功能被保在 _inner 属性里面,通常被称为Angular zone。 这是你执行NgZone.run()时用来运行的回调部分:

run(fn, applyThis, applyArgs) {
    return this._inner.run(fn, applyThis, applyArgs);
}

那么Angular拓展的zone.js的部分被保存在 _outer 属性中,并用于在执行NgZone.runOutsideAngular()时运行回调:

runOutsideAngular(fn) {
    return this._outer.run(fn);
}

这种方法通常用于在Angular区域之外运行性能要求很高的操作,以避免不断触发变更检测。

NgZone有一个isStable属性,用来表示是否没有未完成的宏或微任务。它还定义了四个事件:

+------------------+-----------------------------------------------+
|      Event       |                     Description               |
+------------------+-----------------------------------------------+
| onUnstable       | Notifies when code enters Angular Zone.       |
|                  | This gets fired first on VM Turn.             |
|                  |                                               |
| onMicrotaskEmpty | Notifies when there is no more microtasks     |
|                  | enqueued in the current VM Turn.              |
|                  | This is a hint for Angular to do change       |
|                  | detection which may enqueue more microtasks.  |
|                  | For this reason this event can fire multiple  |
|                  | times per VM Turn.                            |
|                  |                                               |
| onStable         | Notifies when the last `onMicrotaskEmpty` has |
|                  | run and there are no more microtasks, which   |
|                  | implies we are about to relinquish VM turn.   |
|                  | This event gets called just once.             |
|                  |                                               |
| onError          | Notifies that an error has been delivered.    |
+------------------+-----------------------------------------------+

Angular在ApplicationRef中使用onMicrotaskEmpty事件来自动触发变更检测:

this._zone.onMicrotaskEmpty.subscribe(
    {next: () => { this._zone.run(() => { this.tick(); }); }});

没错就是之前手动触发的方法 : app.tick( )

How NgZone implements onMicrotaskEmpty event 那么NgZone是如何实现onMicrotaskEmpty()方法的?

现在让我们看看NgZone是如何实现onMicrotaskEmpty事件的。事件由checkStable函数触发:

function checkStable(zone: NgZonePrivate) {
  if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) {
    try {
      zone._nesting++;
      zone.onMicrotaskEmpty.emit(null); look this line m???er fuck<-------------------

这个函数经常由三个钩子函数触发:

  • onHasTask
  • onInvokeTask
  • onInvoke

 正如在关于Zone的文章中所解释的,当最后两个钩子被触发时,微任务队列可能会发生变化,所以Angular必须在每次触发钩子时运行稳定检查。onhasask钩子也用于执行检查,因为它跟踪整个队列的变化。

Common pitfalls 常见的问题 

注:此章节的问题我个人暂时尚未遇见

stackoveflow中与变更检测相关的一个最常见的问题是,为什么有时在使用第三方库时,组件中的变更没有应用。这里有一个涉及Google API客户端库(gapi)的例子。解决这些问题的常见方法是在Angular zone中运行一个回调,如下所示:

gapi.load('auth2', () => {
    zone.run(() => {
        ...

然而,一个有趣的问题是为什么Zone不注册请求,这导致在一个钩子中没有通知?如果没有通知,NgZone不会自动触发变更检测。

为了了解这一点,我只是深入研究了gapi的源代码,发现它使用JSONP来发出网络请求。这种方法不使用常见的AJAX API,如XMLHttpRequest或Fetch API,这些API由zone修补和跟踪。相反,它创建一个带有源URL的脚本标记,并定义一个全局回调,当从服务器获取带有数据的请求脚本时将触发该回调。zone无法修补或检测到这一点,因此框架对使用这种技术执行的请求保持不知情。

以下是gapi最小化版本的相关片段,供好奇的人参考:

Ja = function(a) {
    var b = L.createElement(Z);
    b.setAttribute(“src”, a);
    a = Ia();
    null !== a && b.setAttribute(“nonce”, a);
    b.async = “true”;
    (a = L.getElementsByTagName(Z)[0]) ? 
        a.parentNode.insertBefore(b, a) : 
        (L.head || L.body || L.documentElement).appendChild(b)
}

变量Z等于"script",参数a保存请求URL:

https://apis.google.com/_.../cb=gapi.loaded_0

 URL的最后一段是gapi.loadd_0全局回调:

typeof gapi.loaded_0 
“function”

你可能感兴趣的:(angular,angular.js,javascript,前端)