Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular 2中的Zone #89

Open
kittencup opened this issue Feb 5, 2016 · 0 comments
Open

Angular 2中的Zone #89

kittencup opened this issue Feb 5, 2016 · 0 comments
Labels

Comments

@kittencup
Copy link
Owner

Angular 2中的Zone

原文地址:http://blog.thoughtram.io//angular/2016/02/01/Zone-in-angular-2.html

理解Zone这篇文章中,在我们的代码中通过构建一个分析异步操作的zone,来探讨Zone的力量。
我们了解到,Zone是一种使我们能够hook到异步任务的执行上下文,如果你还未读过那篇文章,我强烈建议你阅读,因为它是这篇文章的基础,在这篇文章中,我们将需要仔细看看Zone在Angular中扮演的角色。

Zone与Angular完美结合

事实上,Zone很好的解决了,在我们Angular应用中用来执行变化检测的问题。你有没有问自己,何时以及为什么Angular会进行变化检测?是什么告诉Angular “哥们,一个变化可能发生在我的应用中。你能查一下吗?”。

在我们深入这些问题前,让我们先考虑一下究竟是什么原因导致了我们应用的变化。或者更确切地说,有什么可以改变应用的状态。应用状态的改变是由三个因素引起:

  • Events - 使用事件,类似于click, change, input, submit, …
  • XMLHttpRequests - 例如。从远程服务获取数据时
  • Timers - setTimeout(), setInterval()

当然,这三个因素都有一些共同点。你能说出它吗?他们都是异步的。

为什么认为这是很重要的?因为我们发现这些是Angular实际有兴趣去更新视图的唯一情况。

假设我们有一个Angular 2组件,点击一个按钮时执行处理程序:

@Component({
  selector: 'my-component',
  template: `
    <h3>We love {{name}}</h3>
    <button (click)="changeName()">Change name</button>
  `
})
class MyComponent {

  name:string = 'thoughtram';

  changeName() {
    this.name = 'Angular';
  }
}

如果你不熟悉的(click)语法,你可能需要阅读Angular 2模板语法 ,简单的说就是为这个button元素设置一个点击事件处理。

当组件的button被点击,changeName()被执行,从而将改变组件的name属性,由于我们希望这种变化在DOM被反映出来,Angular需要更新相应的视图绑定{{name}},很好,似乎是奇迹般地工作了。

另一个例子是,使用setTimeout()来更新name属性,注意,我们删除了button

@Component({
  selector: 'my-component',
  template: `
    <h3>We love {{name}}</h3>
  `
})
class MyComponent implements OnInit {

  name:string = 'thoughtram';

  ngOnInit() {
    setTimeout(() => {
      this.name = 'Angular';
    }, 1000);
  }
}

我们并没有特别的做什么来告诉的框架, 变化已经发生,没有ng-click,没有$timeout,$scope.$apply()

如果你已经阅读了理解Zone这篇文章,你知道这工作显然是因为Angular2充分利用了Zone,Zone猴子补丁了全局异步操作,如setTimeout()和addEventListener(), 这就是为什么Angular可以很简单的知道什么时候去更新DOM.

事实上,每当VM执行完成会去告诉Angular进行变化检测,就是这么简单:

ObservableWrapper.subscribe(this.zone.onTurnDone, () => {
  this.zone.run(() => {
    this.tick();
  });
});

tick() {
  // perform change detection
  this.changeDetectorRefs.forEach((detector) => {
    detector.detectChanges();
  });
}

每当Angular zone发射出一个onTurnDone事件,它会运行一个任务执行整个应用的变化检测。如果你有兴趣了解Angular2变化检测是如何工作的,关注着吧,我们很快会发表另一篇文章。

等等。onTurnDone事件从哪里发射出来?这是不是默认Zone API的一部分?事实上,Angular引入了名为NgZone的自己的Zone.

Angular 2中的NgZone

NgZone基本上是一个Zone fork出来的,它继承了Zone的API,还添加了一些用来执行上下文额外功能,其中一件事是添加了下面这些我们可以订阅的自定义事件API,因为这些是observable流

  • onTurnStart() - 在Angular事件启动前通知订阅者,发射每一次是由Angular处理的浏览器任务。
  • onTurnDone() - 在Angular zone完成当前任务时立即通知订阅者
  • onEventDone() - 在完成onTurnDone()回调之后在VM事件之前立即通知订阅者, 用来测试验证应用程序状态。

如果"Observables"和"Streams"对于你来说是新的,你可能需要阅读Taking advantage of Observables in Angular 2

主要原因是Angular添加自己的事件发射器,而不是依靠beforeTask和afterTask回调,是为了跟踪定时器和其他微任务。它是非常棒的,使用Observables的API来处理这些事件。

运行代码在Angular Zone外

由于NgZone其实只是全局Zone的一个fork,Angular对于在Zone内需要或不需要执行变化检查,都具有完全的控制权,这为什么是有用的?因为我们并不总是希望Angular神奇地进行变化检测。

正如前面提到过,Zone几乎在任何浏览器的全局异步操作打上了猴子补丁。并且NgZone只是Zone fork出来的,当异步操作发生时就会通知框架进行变化检测,所以当类似于mousemove事件发生时,它也将引发变化检测。

每次鼠标被触发时,我们可能不希望进行变化检测,因为它会减慢我们的应用和结果,是非常糟糕的用户体验。

这就是为什么NgZone带有一个runOutsideAngular()API
,其执行在NgZone的父Zone的给定的任务,这将不发出一个onTurnDone事件,因此,不进行任何改变的检测。为了证明这是非常有用的功能,让我们来看看下面的代码:

@Component({
  selector: 'progress-bar',
  template: `
    <h3>Progress: {{progress}}</h3>
    <button (click)="processWithinAngularZone()">
      Process within Angular zone
    </button>
  `
})
class ProgressBar {

  progress: number = 0;

  constructor(private zone: NgZone) {}

  processWithinAngularZone() {
    this.progress = 0;
    this.increaseProgress(() => console.log('Done!'));
  }
}

在这里没有什么特别的,有一个组件,其模板中的按钮被点击时,会调用processWithinAngularZone()方法。然而,这个方法调用increaseProgress()。让我们来看看这个方法:

increaseProgress(doneCallback: () => void) {
  this.progress += 1;
  console.log(`Current progress: ${this.progress}%`);

  if (this.progress < 100) {
    window.setTimeout(() => {
      this.increaseProgress(doneCallback);
    }, 10);
  } else {
    doneCallback();
  }
}

increaseProgress()每10毫秒调用1次自己,直到调用100次,一旦完成,给定的doneCallback将执行,请注意我们是如何使用的setTimeout()来增长progress值.

在浏览器运行此代码,基本上我们已经知道代码将如何展示,每一次setTimeout()调用,Angular会执行变化检测和更新视图,我们会看到每10毫秒progress被增长,当在Angular的区外运行这段代码将变得更有趣,让我们添加一个方法,就是这样。

processOutsideAngularZone() {
  this.progress = 0;
  this.zone.runOutsideAngular(() => {
    this.increaseProgress(() => {
      this.zone.run(() => {
        console.log('Outside Done!');
      });
    });
  });
}

processOutsideAngularZone()也调用increaseProgress(),但这次使用runOutsideAngularZone(),它会导致在每次timeout后Angular不会通知执行变化检测,我们可以在组件里注入token为NgZone,来访问Angular zone.

progress增加时UI不会更新,然而,一旦increaseProgress()完成后,我们使用zone.run()在Angular zone 内运行另一个任务,这又导致Angular 来执行变化检测并更新视图,换句话说,我们不会看到progress不断增加,一旦完成我们只看到progress最后的值,在这里可以看到全部的代码

Zone 现在也被提议在TC39作为标准,也许是另一个理由让我们来仔细学习它

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant