【Angular】关于AsyncPipe你不知道的3件事!

来源:互联网 发布:惠州人民政府网络问政 编辑:程序博客网 时间:2024/05/18 12:32

我的angular2项目:
http://git.oschina.net/zt_zhong/CodeBe
原文地址:https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know-about-the-async-pipe.html

你肯定听说过Angular的AsyncPipe对不对?它是一个非常方便的管道(Pipe),可以让我们在模版中使用,所以我们不需要去处理从Observables或者Promises中的数据。 结果就是,AsyncPipe有一些神奇的功能,可能不是那么明显。在这篇文章中,我们将会对这个有用的小工具的内部工作原理做一些阐述。

订阅到Observables

通常我们考虑使用AsyncPipe时,只是用它来解析从http调用中返回的数据。我们发出一个http调用,返回一个Observable对象,然后做一些转换(比如:map(…).filter(…)),最后将一个Observable暴露给我们组件的模板。这里是就是我们通常使用的时候的样子:

...@Component({  ...  template: `    <md-list>      <a md-list-item        *ngFor="let contact of contacts | async"        title="View {{contact.name}} details">        <img md-list-avatar [src]="contact.image" alt="Picture of {{contact.name}}">        <h3 md-line>{{contact.name}}</h3>      </a>    </md-list>`,})export class ContactsListComponent implements OnInit {  contacts: Observable<Array<Contact>>;  constructor(private contactsService: ContactsService) {}  ngOnInit () {    this.contacts = this.contactsService.getContacts();  }}

在这种情况下,我们的Observable 就是我们所说的短命(原文short-lived)的。Observable 在这种情况下只发射一个值,就是一个contacts数组,并在发射之后结束。这是使用http的典型情况,它基本上是使用Promises的唯一场景。

然而,我们可以完全拥有发出多个值的Observables。想想使用websockets的例子。我们可能有一个随着时间的推移而构建的数组!我们来模拟一个可以发出数字数组的Observable。但是,不是一次只发出一个数组,它会在每次添加新项目时发出一个数组。为了不让数组无限增长,我们将其限制为最后五个项目。

...@Component({  selector: 'my-app',  template: `    <ul>      <li *ngFor="let item of items | async">{{item}}</li>    </ul>`})export class AppComponent {  items = Observable.interval(100)                    .scan((acc, cur)=>[cur, ...acc].slice(0, 5), []);             }

注意我们的列表如何保持良好的同步,不用多说,感谢AsyncPipe!

跟踪引用

如果没有AsyncPipe的帮助,让我们回到上面的代码并重构上面的代码。但是,在这里我们使用一个按钮来重新生成数字,并且在每次重新生成这个序列的时候随机选择元素的背景色。

...@Component({  selector: 'my-app',  template: `    <button (click)="newSeq()">New random sequence</button>    <ul>      <li [style.background-color]="item.color"          *ngFor="let item of items">{{item.num}}</li>    </ul>`})export class AppComponent {  items = [];  constructor () {    this.newSeq();  }  newSeq() {    // generate a random color    let color = '#' + Math.random().toString(16).slice(-6);    Observable.interval(1000)          .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), [])          .subscribe(items => this.items = items);  }}


在线运行地址

运行这个例子,注意到什么了吗?随着按钮的每次点击,颜色会在越来越多的不同颜色之间来回滚动。那就是因为在这种情况下,Observable就是我们所说的长寿(原文:long-lived)的。
此外,每次我们点击按钮,我们都在创建另外一个这些长寿的Observable,而没有清理以前的。

让我们重构代码来跟踪订阅,并在每次创建一个新的Observable时,都会删除我们长寿的Observable。

...export class AppComponent {  ...  subscription: Subscription;  newSeq() {    if (this.subscription) {      this.subscription.unsubscribe();    }    // generate a random color    let color = '#' + Math.random().toString(16).slice(-6);    this.subscription = Observable.interval(1000)          .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), [])          .subscribe(items => this.items = items);  }}

在线运行地址
每次我们订阅一个Observable ,我们将它保存到组件实例的一个属性中。然后,当我们再次运行newSeq的时候,我们检查this.subscription是否存在,如果存在的话,我们就需要去调用unsubscribe来取消订阅。这就是为什么我们看不到我们的列表在各种颜色之间跳转,不管我们点击了按钮多少次。

现在我们再次看下AsyncPipe。让我们来改变ngFor来应用AsyncPipe。

@Component({  selector: 'my-app',  template: `    <button (click)="newSeq()">New random sequence</button>    <ul>      <li [style.background-color]="item.color"          *ngFor="let item of items | async">{{item.num}}</li>    </ul>`})export class AppComponent {  items: Observable<any>;  constructor () {    this.newSeq();  }  newSeq() {    // generate a random color    let color = '#' + Math.random().toString(16).slice(-6);    this.items = Observable.interval(1000)                           .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []);  }}

在线运行地址
我确信你已经听说AsyncPipe一旦组件被销毁,就会从Observables取消订阅。但是,一旦表达式的引用发生变化,你也知道它取消订阅吗?没错,只要我们为此分配一个新的Observable,则AsyncPipe将自动取消订阅先前绑定的Observable!这不仅使我们的代码变得美观干净,还能保护我们免受非常微妙的内存泄漏。

标记要检查的东西

好的。 我们为您提供最后一个漂亮的AsyncPipe功能!如果你已经阅读了我们的关于Angular变更检测的文章,你应该知道,你可以使用OnPush策略来进一步加快变更检测过程。我们重构我们的例子,并引入一个SeqComponent来显示序列,而我们的根组件将管理数据并通过输入绑定传递它。

我们开始创建一个非常简单的SeqComponent。

@Component({  selector: 'my-seq',  template: `    <ul>      <li [style.background-color]="item.color"           *ngFor="let item of items">{{item.num}}</li>    </ul>`})export class SeqComponent {  @Input()  items: Array<any>;}

请注意@Input()装饰器items属性,这意味着组件将通过属性绑定从外部接收。
我们的根组件维护一个数组seqs,并通过点击一个按钮将新的长寿的Observables推入它。
它使用* ngFor将这些Observables中的每一个传递给一个新的SeqComponent实例。还要注意,我们在我们的属性绑定表达式([items] =“seq | async”)中使用AsyncPipe来传递纯数组而不是Observable,因为这是SeqComponent所期望的。

@Component({  selector: 'my-app',  template: `    <button (click)="newSeq()">New random sequence</button>    <ul>      <my-seq *ngFor="let seq of seqs" [items]="seq | async"></my-seq>    </ul>`})export class AppComponent {  seqs = [];  constructor () {    this.newSeq();  }  newSeq() {    // generate a random color    let color = '#' + Math.random().toString(16).slice(-6);    this.seqs.push(Observable.interval(1000)                           .scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []));  }}

到目前为止,我们还没有对潜在的变更检测策略做出任何改变。 如果您点击按钮几次,请注意我们如何获取多个列表,以不同的时间独立更新。
在线运行地址
然而,在变更检测方面,这意味着每次Observables 都可以检查所有组件。这是浪费资源。通过将SeqComponent的更改检测设置为OnPush,我们可以做得更好,这意味着如果输入(我们的案例中的数组)发生变化,它将只检查它的绑定。

@Component({  changeDetection: ChangeDetectionStrategy.OnPush,  selector: 'my-seq',  ...})

这非常有效。但是这里有个问题:它只在这种情况下起作用,因为我们的Observable 每次都是创建一个新的数组和值。即使这实际上并不太糟糕,实际上在大多数情况下都是有益的,让我们考虑一下我们使用一个不同的实现来修改现有的数组,而不是每次重新创建它。

Observable.interval(1000)          .scan((acc, num)=>{            acc.splice(0, 0, {num, color});            if (acc.length > 5) {              acc.pop()            }            return acc;          }, [])

如果我们尝试,OnPush似乎不再工作,因为项目的引用根本不会改变。 实际上,当我们尝试这样做时,我们看到每个列表都不会超出其第一个元素。
在线运行地址

再次认识AsyncPipe! 我们来改变我们的SeqComponent,所以它需要一个Observable而不是一个数组作为它的输入。

@Component({  changeDetection: ChangeDetectionStrategy.OnPush,  selector: 'my-seq',  template: `    <ul>      <li [style.background-color]="item.color"           *ngFor="let item of items | async">{{item.num}}</li>    </ul>`})export class SeqComponent {  @Input()  items: Observable<Array<any>>;}

另请注意,它现在将AsyncPipe应用于其模板,因为它不再处理一个简单的数组。 我们的AppComponent还需要更改,以便不再在属性绑定中应用AsyncPipe。

<ul>  <my-seq *ngFor="let seq of seqs" [items]="seq"></my-seq></ul>

在线运行地址

cool!现在似乎已经开始有效了。

让我们回顾一下,我们的数组实例不会改变,我们的Observable的实例也不会改变。那么为什么OnPush在这种情况下起作用了? 原因可以在AsyncPipe本身的源代码中找到

private _updateLatestValue(async: any, value: Object): void {  if (async === this._obj) {    this._latestValue = value;    this._ref.markForCheck();  }}

AsyncPipe标记要检查的组件的ChangeDetectorRef,有效地告诉变更检测,该组件可能会发生更改。如果您想更详细地了解我们如何工作,我们建议您阅读我们深入的变更检测文章。

总结

我们使用AsyncPipe作为一个漂亮的小工具来在我们的组件中保存几行代码。 实际上,它隐藏了来自我们的管理异步任务的很多复杂性。

原创粉丝点击